Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

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

Tech 1

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

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

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...

Leave a Comment

Anonymous

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