<%@ Page Title="Strategy: Direct-to-S3 multipart" Language="C#" MasterPageFile="~/Site.master" AutoEventWireup="true" %> 
<%@ Import Namespace="AjaxUploader.Providers" %> 
<asp:Content ID="TitleContent" ContentPlaceHolderID="TitleContent" runat="server">Direct-to-S3 multipart - AjaxUploader v5.3 Demos</asp:Content> 
<asp:Content ID="HeaderContent" ContentPlaceHolderID="HeaderTitle" runat="server">Direct-to-S3 multipart</asp:Content> 
<asp:Content ID="BodyContent" ContentPlaceHolderID="MainContent" runat="server"> 
<% var s3SignerReady = AjaxUploader.Providers.S3.Signer != null; %> 
<div class="demo-card"> <div class="demo-card-header">Browser-direct S3 multipart upload</div> <div class="demo-card-body"> <p class="demo-description"> The browser uploads each part directly to Amazon S3 (or MinIO, Backblaze B2, Cloudflare R2, Wasabi - anything S3-API compatible) via presigned URLs your server signs on demand. The WebForms handler only signs - the bytes bypass IIS entirely. Survives reloads: the resumed task reuses the same <code>uploadId</code> and part ETags via IndexedDB.
 </p> <div class="demo-expect"><strong>What you should see:</strong> 5&nbsp;MB parts uploaded straight to S3 with presigned URLs, completed as a multipart upload. 5&nbsp;MB is S3's minimum part size for all but the last part.</div> <% if (!s3SignerReady) { %> <div class="alert alert-warning mt-3" role="alert"> <strong>Setup required.</strong> No <code>IS3Signer</code> is registered yet, so <code>/ajaxupload.axd/s3/*</code> currently returns 501. To activate this demo:
 <ol class="mt-2 mb-0"> <li>Install <code>AWSSDK.S3</code> into <code>bin/</code> (from NuGet).</li> <li>Move <code>v5-signers/DemoS3Signer.cs.template</code> -> <code>App_Code/DemoS3Signer.cs</code>.</li> <li>Fill in <code>DemoS3:*</code> values in <code>web.config</code>.</li> <li>Recycle the app pool.</li> </ol> Full walk-through: <a href="<%= ResolveUrl("~/v5-signers/README.txt") %>"><code>v5-signers/README.txt</code></a>.
 </div> <% } else { %> <div class="alert alert-success mt-3" role="alert"> <strong>Signer active.</strong> <code>IS3Signer</code> is registered (<code><%= Server.HtmlEncode(AjaxUploader.Providers.S3.Signer.GetType().Name) %></code>). The uploader below will upload directly to your bucket.
 </div> <div class="d-flex gap-2 mb-2 mt-4">
 <button type="button" class="btn btn-primary" id="startBtn_s3Uploader">Start upload</button>
 <button type="button" class="btn btn-outline-secondary" id="clearBtn_s3Uploader">Clear queue</button>
 </div>
 <div id="s3Uploader"></div> <script> (function () {
 function init() {
 if (!window.AjaxUploader || typeof AjaxUploader.create !== 'function') return; // graceful no-op if runtime missing
 var startButton = document.getElementById('startBtn_s3Uploader');
 var clearButton = document.getElementById('clearBtn_s3Uploader');
 var uploader = AjaxUploader.create('#s3Uploader', {
 uploadUrl: '<%= ResolveUrl("~/ajaxupload.axd/upload") %>',
 strategy: 's3',
 chunkSize: 5 * 1024 * 1024,
 chunkConcurrency: 4,
 autoUpload: false,
 multiple: true,
 persistState: true,
 persistAdapter: 'indexeddb'
 });
 if (startButton) startButton.addEventListener('click', function () { if (uploader && typeof uploader.upload === 'function') uploader.upload(); });
 if (clearButton) clearButton.addEventListener('click', function () { if (uploader && typeof uploader.clear === 'function') uploader.clear(); });
 }
 if (document.readyState !== 'loading') init();
 else document.addEventListener('DOMContentLoaded', init);
 })();
 </script> <% } %> <h5 class="mt-4">Client config</h5> <div class="code-panel"> 
<pre><code>AjaxUploader.create('#uploader', {
 uploadUrl: '<%= ResolveUrl("~/ajaxupload.axd/upload") %>',
 strategy: 's3', <span style="color:#94a3b8">// selects _s3DirectUpload()</span> chunkSize: 5 * 1024 * 1024, <span style="color:#94a3b8">// S3 5 MB minimum part size</span> chunkConcurrency: 4, <span style="color:#94a3b8">// parallel PUTs</span> persistState: true,
 persistAdapter: 'indexeddb' <span style="color:#94a3b8">// resume survives reload</span> 
});</code></pre> </div> <h5 class="mt-4">Server-side (Global.asax.cs)</h5> <div class="code-panel"> 
<pre><code>using AjaxUploader.Providers;

protected void Application_Start(object sender, EventArgs e)
{
 <span style="color:#94a3b8">// Your IS3Signer wraps AmazonS3Client (or MinIO client).</span> S3.Signer = new MyS3Signer(
 bucketName: "your-bucket",
 region: "us-east-1",
 accessKeyId: ConfigurationManager.AppSettings["AWS:AccessKeyId"],
 secretKey: ConfigurationManager.AppSettings["AWS:SecretKey"]);
}</code></pre> </div> <h5 class="mt-4">Wire protocol</h5> <ul class="mb-3"> <li><code>POST /ajaxupload.axd/s3/create</code> -> <code>{ uploadId, key }</code></li> <li><code>POST /ajaxupload.axd/s3/sign</code> -> <code>{ uploadId, key, partNumbers }</code> -> <code>{ urls }</code></li> <li><code>PUT &lt;signedUrl&gt;</code> -> S3 returns <code>ETag</code> per part (browser-direct)</li> <li><code>POST /ajaxupload.axd/s3/complete</code> -> <code>{ uploadId, key, parts:[{PartNumber, ETag}] }</code></li> <li><code>POST /ajaxupload.axd/s3/abort</code> on cancel / fatal error</li> </ul> <h5 class="mt-4">Bucket CORS</h5> <p>The browser PUTs directly to S3, so the bucket must allow cross-origin PUT and expose the <code>ETag</code> header:</p> <div class="code-panel"> 
<pre><code>[{
 "AllowedOrigins": ["https://ajaxuploader.com"],
 "AllowedMethods": ["PUT"],
 "AllowedHeaders": ["*"],
 "ExposeHeaders": ["ETag"],
 "MaxAgeSeconds": 3000
}]</code></pre> </div> </div> 
</div> 
</asp:Content> 
