Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/masastack/MASA.Framework/llms.txt

Use this file to discover all available pages before exploring further.

MASA Framework’s Object Storage building block wraps cloud blob storage behind a provider-agnostic interface. Your service code calls IObjectStorageClient methods — upload, download, delete, check existence — and the underlying cloud provider (Alibaba OSS, AWS S3, Azure Blob, etc.) is swapped in through the registration layer without touching a single line of business logic.

Installation

dotnet add package Masa.BuildingBlocks.Storage.ObjectStorage  # core interface
dotnet add package Masa.Contrib.Storage.ObjectStorage.Aliyun  # Alibaba OSS provider
Replace the provider package with whichever backend your infrastructure uses.

Core Interface: IObjectStorageClient

Inject IObjectStorageClient to interact with object storage. The full method surface is:
MethodReturnsDescription
GetObjectAsync(bucket, objectName, callback)TaskStream the full object to a callback
GetObjectAsync(bucket, objectName, offset, length, callback)TaskStream a byte range of the object
PutObjectAsync(bucket, objectName, stream)TaskUpload an object from a stream
ObjectExistsAsync(bucket, objectName)Task<bool>Check if an object exists
DeleteObjectAsync(bucket, objectName)TaskDelete a single object
DeleteObjectsAsync(bucket, objectNames)TaskBatch delete multiple objects
GetObjectMetadataAsync(bucket, objectName)Task<...>Retrieve object metadata (content type, size, ETag, etc.)
GetSecurityToken()TemporaryCredentialsResponseObtain short-lived STS credentials for client-side uploads
GetToken()stringObtain a pre-signed URL or access token

Supporting Types

TypeDescription
IObjectStorageClientFactoryCreates named IObjectStorageClient instances for multi-bucket or multi-provider setups
AbstractStorageClientBase class; override to implement a custom provider
IManualObjectStorageClientLow-level interface that AbstractStorageClient implements
BucketNameAttributeAnnotate a class to bind it to a specific bucket name
BucketNameProviderResolves the bucket name for a given type at runtime
BucketNamesRegistry that maps type names to bucket names
TemporaryCredentialsResponseSTS credential payload returned by GetSecurityToken()

Registration

builder.Services.AddObjectStorage(options =>
    options.UseAliyunStorage());
Provider-specific configuration (endpoint, access key, secret) is typically read from appsettings.json or environment variables. Refer to the chosen provider package’s README for its configuration schema.

Basic Usage

public class FileService
{
    private readonly IObjectStorageClient _storage;

    public FileService(IObjectStorageClient storage) => _storage = storage;

    public async Task UploadAsync(string fileName, Stream content)
        => await _storage.PutObjectAsync("my-bucket", fileName, content);

    public async Task DownloadAsync(string fileName, Action<Stream> handler)
        => await _storage.GetObjectAsync("my-bucket", fileName, handler);

    public async Task<bool> ExistsAsync(string fileName)
        => await _storage.ObjectExistsAsync("my-bucket", fileName);

    public async Task DeleteAsync(string fileName)
        => await _storage.DeleteObjectAsync("my-bucket", fileName);
}

Partial / Range Downloads

For large files, download only the bytes you need by specifying an offset and length:
// Download bytes 0–4095 (the first 4 KB) of a file
await _storage.GetObjectAsync(
    "my-bucket",
    "large-video.mp4",
    offset: 0,
    length: 4096,
    callback: stream =>
    {
        // process the partial stream
    });

Batch Delete

Remove multiple objects in a single API call to reduce round-trips:
var staleFiles = new[]
{
    "exports/report-2023-01.csv",
    "exports/report-2023-02.csv",
    "exports/report-2023-03.csv"
};

await _storage.DeleteObjectsAsync("my-bucket", staleFiles);

Temporary Credentials for Client-Side Uploads

Generating short-lived STS credentials lets front-end clients upload directly to object storage without routing large payloads through your service:
[ApiController]
[Route("storage")]
public class StorageController : ControllerBase
{
    private readonly IObjectStorageClient _storage;

    public StorageController(IObjectStorageClient storage) => _storage = storage;

    [HttpGet("credentials")]
    public IActionResult GetUploadCredentials()
    {
        var credentials = _storage.GetSecurityToken();
        return Ok(credentials); // return to client; expires per STS policy
    }
}

Bucket Binding with BucketNameAttribute

Avoid scattering hardcoded bucket name strings throughout your codebase by binding a marker class to a bucket:
[BucketName("product-images")]
public class ProductImagesBucket { }

public class ProductImageService
{
    private readonly IObjectStorageClient _storage;
    private readonly BucketNameProvider _bucketNames;

    public ProductImageService(
        IObjectStorageClient storage,
        BucketNameProvider bucketNames)
    {
        _storage = storage;
        _bucketNames = bucketNames;
    }

    public async Task UploadImageAsync(string fileName, Stream image)
    {
        var bucket = _bucketNames.GetBucketName<ProductImagesBucket>();
        await _storage.PutObjectAsync(bucket, fileName, image);
    }
}

Multiple Storage Backends

When your application needs to talk to more than one storage provider or bucket configuration, use IObjectStorageClientFactory to retrieve named clients:
public class MultiStorageService
{
    private readonly IObjectStorageClient _primary;
    private readonly IObjectStorageClient _archive;

    public MultiStorageService(IObjectStorageClientFactory factory)
    {
        _primary = factory.Create("Primary");
        _archive = factory.Create("Archive");
    }

    public async Task ArchiveAsync(string objectName)
    {
        // Download from primary, upload to archive
        await _primary.GetObjectAsync("live-bucket", objectName, async stream =>
        {
            await _archive.PutObjectAsync("archive-bucket", objectName, stream);
        });

        await _primary.DeleteObjectAsync("live-bucket", objectName);
    }
}
Always dispose or fully consume the stream inside the GetObjectAsync callback before returning. The framework closes the underlying HTTP response stream when the callback returns, so keeping a reference to the stream after the callback exits will result in an ObjectDisposedException.

Build docs developers (and LLMs) love