Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Direct Browser Uploads to Alibaba Cloud OSS in ASP.NET Core

Tech Apr 18 30

Distributed deployments present unique challenges for file storage. When applications run behind load balancers, saving uploads to local filesystems creates synchronization issues across instances. Centralized object storage eliminates this complexity while enabling horizontal scaling.

Alibaba Cloud OSS supports direct browser uploads using POST policies and temporary signatures. This approach bypasses application servers for data transfer, reducing bandwidth costs and eliminating the need for shared network storage.

Backend Credential Generation

Create a service to generate temporary upload credentials with restricted permissions:

public class OssCredentialProvider
{
    private readonly string _accessKey;
    private readonly string _secretKey;
    private readonly string _bucket;
    private readonly string _regionEndpoint;
    
    public OssCredentialProvider(IConfiguration configuration)
    {
        _accessKey = configuration["Aliyun:Oss:AccessKeyId"];
        _secretKey = configuration["Aliyun:Oss:AccessKeySecret"];
        _bucket = configuration["Aliyun:Oss:BucketName"];
        _regionEndpoint = configuration["Aliyun:Oss:Endpoint"];
    }
    
    public SecureUploadToken CreateUploadToken(int resourceType)
    {
        var storagePrefix = DetermineResourcePath(resourceType);
        var ossClient = new OssClient(_regionEndpoint, _accessKey, _secretKey);
        
        var expiration = DateTime.UtcNow.AddMinutes(10);
        var policyConstraints = new PolicyConditions();
        policyConstraints.AddConditionItem("bucket", _bucket);
        policyConstraints.AddConditionItem(MatchMode.StartWith, PolicyConditions.CondKey, storagePrefix);
        policyConstraints.AddConditionItem(PolicyConditions.CondContentLengthRange, 1024, 10485760);
        
        var policyDocument = ossClient.GeneratePostPolicy(expiration, policyConstraints);
        var base64Policy = Convert.ToBase64String(Encoding.UTF8.GetBytes(policyDocument));
        var securitySignature = GenerateSignature(_secretKey, base64Policy);
        
        return new SecureUploadToken
        {
            StoragePrefix = storagePrefix,
            TargetBucket = _bucket,
            CredentialId = _accessKey,
            PolicyString = base64Policy,
            SecurityHash = securitySignature,
            UploadUrl = $"https://{_bucket}.{_regionEndpoint}"
        };
    }
    
    private string DetermineResourcePath(int category)
    {
        var mappings = new Dictionary<int, string>
        {
            [1] = "content/articles",
            [2] = "media/carousels", 
            [3] = "branding/partners",
            [4] = "user-generated/attachments"
        };
        
        return mappings.TryGetValue(category, out var path) 
            ? $"production/{path}" 
            : "production/misc";
    }
    
    private string GenerateSignature(string secret, string payload)
    {
        using var algorithm = new HMACSHA1(Encoding.UTF8.GetBytes(secret));
        var signatureBytes = algorithm.ComputeHash(Encoding.UTF8.GetBytes(payload));
        return Convert.ToBase64String(signatureBytes);
    }
}

public class SecureUploadToken
{
    public string StoragePrefix { get; set; }
    public string TargetBucket { get; set; }
    public string CredentialId { get; set; }
    public string PolicyString { get; set; }
    public string SecurityHash { get; set; }
    public string UploadUrl { get; set; }
}

Front end Implementation

Construct a hidden form to submit files directly to OSS endpoints:

<form id="cloud-storage-form" method="POST" enctype="multipart/form-data" style="display:none;">
    <input type="hidden" name="key" id="storage-path" />
    <input type="hidden" name="bucket" id="target-bucket" />
    <input type="hidden" name="OSSAccessKeyId" id="access-credential" />
    <input type="hidden" name="policy" id="encoded-policy" />
    <input type="hidden" name="Signature" id="request-signature" />
    <input type="hidden" name="success_action_status" value="200" />
    <input type="file" name="file" id="asset-selector" accept="image/jpeg,image/png,image/webp" />
</form>

Handle token acquisition and submission:

const UploadManager = {
    async retrieveSecureToken(assetCategory) {
        const response = await fetch(`/api/storage/token?category=${assetCategory}`);
        if (!response.ok) throw new Error('Failed to obtain upload credentials');
        return await response.json();
    },

    configureForm(tokenData) {
        document.getElementById('storage-path').value = tokenData.storagePrefix;
        document.getElementById('target-bucket').value = tokenData.targetBucket;
        document.getElementById('access-credential').value = tokenData.credentialId;
        document.getElementById('encoded-policy').value = tokenData.policyString;
        document.getElementById('request-signature').value = tokenData.securityHash;
        document.getElementById('cloud-storage-form').action = tokenData.uploadUrl;
    },

    generateUniqueKey(prefix, originalFilename) {
        const timestamp = new Date().getTime();
        const entropy = Math.random().toString(36).substring(2, 10);
        const extension = originalFilename.substring(originalFilename.lastIndexOf('.'));
        return `${prefix}/${timestamp}-${entropy}${extension}`;
    },

    async uploadAsset(fileInput, category) {
        const token = await this.retrieveSecureToken(category);
        this.configureForm(token);
        
        const file = fileInput.files[0];
        const uniquePath = this.generateUniqueKey(token.storagePrefix, file.name);
        document.getElementById('storage-path').value = uniquePath;
        
        const formElement = document.getElementById('cloud-storage-form');
        const formData = new FormData(formElement);
        
        const response = await fetch(formElement.action, {
            method: 'POST',
            body: formData,
            mode: 'cors'
        });
        
        if (response.ok) {
            return `${token.uploadUrl}/${uniquePath}`;
        }
        throw new Error('Upload to cloud storage failed');
    }
};

// Initialize on page load
document.addEventListener('DOMContentLoaded', () => {
    UploadManager.retrieveSecureToken(1).then(token => {
        UploadManager.configureForm(token);
    });
});

Rich Text Editor Integration

For TinyMCE integration, override the default upload handler to route through OSS:

tinymce.init({
    selector: '#content-editor',
    images_upload_handler: (blobInfo, progress) => {
        return new Promise((resolve, reject) => {
            const blob = blobInfo.blob();
            const mockInput = { files: [blob] };
            
            UploadManager.uploadAsset(mockInput, 1)
                .then(url => resolve(url))
                .catch(error => reject(error.message));
        });
    },
    automatic_uploads: true,
    file_picker_types: 'image'
});

Image Processing Optimization

Leverage OSS Image Service (IMG) for on-the-fly transformatoins instead of storing multiple variants:

  • Responsive sizing: Append ?x-oss-process=image/resize,w_800 to generate 800px width versions
  • Format optimization: Use ?x-oss-process=image/format,webp for modern browser support
  • Watermarking: Apply ?x-oss-process=image/watermark,text_SG9zdGVkIEJ5,size_30 for branding
  • Quality adjustment: Add ,q_80 to balance quality and file size

Configure CORS rules in the OSS console to permit POST requests from you're application domain. Specify allowed origins, enable the POST method, and include Content-Type in exposed headers.

Related Articles

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

SBUS Signal Analysis and Communication Implementation Using STM32 with Fus Remote Controller

Overview In a recent project, I utilized the SBUS protocol with the Fus remote controller to control a vehicle's basic operations, including movement, lights, and mode switching. This article is aimed...

Understanding the MP4 File Format and Analysis

Table of Contents Overview Fundamentals of the MP4 Format Key Concepts of the Container Format Box Structure Track Samples Sample Tables Chunks Detailed Explanation of Core Boxes Additional Boxes...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.