Skip to main content

Google Cloud Storage Provider

Vali-Blob.GCP provides GCPStorageProvider, implementing IStorageProvider, IResumableUploadProvider, and IPresignedUrlProvider backed by Google Cloud Storage (GCS) via Google.Cloud.Storage.V1.


Installation

dotnet add package Vali-Blob.Core
dotnet add package Vali-Blob.GCP

GCPStorageOptions Reference

OptionTypeRequiredDescription
ProjectIdstringYesGCP project ID, e.g. "my-project-12345".
BucketNamestringYesGCS bucket name for all operations.
JsonCredentialsstring?NoService account key JSON as a string. Omit to use Application Default Credentials (ADC).
DefaultStorageClassstring?NoDefault storage class: "STANDARD", "NEARLINE", "COLDLINE", "ARCHIVE". Default: null (bucket default).

DI Registration

With Service Account Key

using Vali-Blob.Core;
using Vali-Blob.GCP;

builder.Services
.AddVali-Blob(o => o.DefaultProvider = "gcp")
.AddProvider<GCPStorageProvider>("gcp", opts =>
{
opts.ProjectId = builder.Configuration["GCP:ProjectId"]!;
opts.BucketName = builder.Configuration["GCP:BucketName"]!;
opts.JsonCredentials = builder.Configuration["GCP:ServiceAccountJson"]!;
})
.WithPipeline(p => p
.UseValidation(v =>
{
v.MaxFileSizeBytes = 500_000_000;
v.AllowedExtensions = [".jpg", ".png", ".pdf", ".mp4"];
})
.UseContentTypeDetection()
.UseConflictResolution(ConflictResolution.ReplaceExisting)
);

appsettings.json

{
"GCP": {
"ProjectId": "my-project-12345",
"BucketName": "my-app-uploads"
}
}
Never commit service account keys

Store the JSON key in dotnet user-secrets for development, or load it from an environment variable or secret manager. Never commit .json key files to source control.


Application Default Credentials (ADC)

When running on GCP (GKE, Cloud Run, Compute Engine, App Engine), use Application Default Credentials instead of a service account key file. ADC resolves credentials automatically from the runtime environment:

builder.Services
.AddVali-Blob(o => o.DefaultProvider = "gcp")
.AddProvider<GCPStorageProvider>("gcp", opts =>
{
opts.ProjectId = builder.Configuration["GCP:ProjectId"]!;
opts.BucketName = builder.Configuration["GCP:BucketName"]!;
// JsonCredentials omitted — ADC used automatically
});

For local development with ADC, authenticate with the gcloud CLI:

gcloud auth application-default login
ADC credential chain

ADC tries, in order: GOOGLE_APPLICATION_CREDENTIALS environment variable → Well-known credential file → GCE metadata server. On GKE with Workload Identity or Cloud Run, service account credentials are resolved with no configuration needed.


Creating a Service Account

  1. Open the GCP Console
  2. Navigate to IAM & AdminService Accounts
  3. Click Create Service Account
  4. Assign the following roles on the bucket:
RoleRequired For
Storage Object AdminUpload, download, delete, copy, metadata
Storage Legacy Bucket ReaderListFilesAsync, ListFoldersAsync

For minimum-privilege environments, use a custom role with only:

  • storage.objects.create
  • storage.objects.get
  • storage.objects.delete
  • storage.objects.list
  • storage.buckets.get
  1. Under the Keys tab, click Add KeyCreate new keyJSON → download

Load the key in your application:

// From environment variable (recommended for containers)
opts.JsonCredentials = Environment.GetEnvironmentVariable("GCP_SERVICE_ACCOUNT_JSON")
?? throw new InvalidOperationException("GCP_SERVICE_ACCOUNT_JSON is not set.");

Creating a Bucket

# Create a bucket in a specific region
gcloud storage buckets create gs://my-app-uploads \
--project=my-project-12345 \
--location=us-central1 \
--uniform-bucket-level-access

# Verify
gcloud storage buckets describe gs://my-app-uploads

GCS Storage Classes

ClassMin RetentionUse Case
STANDARDNoneFrequently accessed data. Highest cost, lowest retrieval cost.
NEARLINE30 daysMonthly access. ~50% lower storage cost.
COLDLINE90 daysQuarterly access. Lower cost, retrieval fees apply.
ARCHIVE365 daysLong-term backup. Lowest cost, highest retrieval cost.

Use lifecycle rules to automatically transition objects between classes:

  1. GCP Console → your bucket → Lifecycle tab
  2. Add rule: e.g., move to NEARLINE after 30 days, COLDLINE after 90 days

Presigned URLs (V4 Signed URLs)

GCPStorageProvider implements IPresignedUrlProvider using GCS V4 signed URLs. Signed URLs require a service account key (ADC cannot sign URLs) because the URL is signed with the service account's private key:

var provider = factory.Create("gcp");

if (provider is IPresignedUrlProvider presigned)
{
// V4 signed PUT URL — client uploads directly to GCS for 15 minutes
var uploadUrl = await presigned.GetPresignedUploadUrlAsync(
StoragePath.From("uploads", userId, "avatar.jpg"),
expiresIn: TimeSpan.FromMinutes(15));

// V4 signed GET URL — time-limited access to a private object
var downloadUrl = await presigned.GetPresignedDownloadUrlAsync(
"private/salary-report.pdf",
expiresIn: TimeSpan.FromHours(1));

return Results.Ok(new
{
uploadUrl = uploadUrl.Value,
downloadUrl = downloadUrl.Value
});
}

V4 signed URLs have a maximum validity of 7 days. The URL is self-contained — no server-side revocation or tracking is required.


CORS Configuration for Direct Client Uploads

When clients upload directly via signed URLs, configure CORS on the bucket:

cat > cors.json << 'EOF'
[
{
"origin": ["https://myapp.com"],
"method": ["PUT", "GET", "HEAD"],
"responseHeader": ["Content-Type", "ETag", "x-goog-meta-*"],
"maxAgeSeconds": 3600
}
]
EOF

gcloud storage buckets update gs://my-app-uploads --cors-file=cors.json

Local Development with Fake GCS Server

Use fake-gcs-server to emulate GCS locally without a GCP account:

docker run -d --name fake-gcs \
-p 4443:4443 \
fsouza/fake-gcs-server \
-scheme http -port 4443

Configure Vali-Blob to use the emulator:

.AddProvider<GCPStorageProvider>("gcp", opts =>
{
opts.ProjectId = "test-project";
opts.BucketName = "dev-bucket";
opts.JsonCredentials = "{\"type\":\"service_account\"}"; // minimal, not validated by emulator
// Set STORAGE_EMULATOR_HOST=http://localhost:4443 in environment
})

The Google Cloud Storage SDK respects the STORAGE_EMULATOR_HOST environment variable automatically.


Resumable Uploads (GCS Resumable Upload API)

Vali-Blob maps IResumableUploadProvider to the GCS Resumable Upload API:

Vali-Blob OperationGCS API Operation
StartResumableUploadAsyncInitiate resumable upload session (returns upload URI)
UploadChunkAsyncUpload chunk to session URI with Content-Range
CompleteResumableUploadAsyncFinal chunk upload signals completion
AbortResumableUploadAsyncDELETE request to session URI

GCS resumable uploads support chunks as small as 256 KiB (must be a multiple of 256 KiB except for the last chunk). Recommended chunk size: 8 MiB.


Supported Operations

OperationSupportedNotes
UploadAsyncYesSingle PUT or resumable
DownloadAsyncYesIncluding byte range
DeleteAsyncYes
DeleteFolderAsyncYesBatch list + delete by prefix
ExistsAsyncYesObjects.Get with fields=name
CopyAsyncYesServer-side copy
GetMetadataAsyncYesObject metadata + custom metadata
SetMetadataAsyncYesPatch object metadata
ListFilesAsyncYesObjects.List with prefix + pagination
ListFoldersAsyncYesObjects.List with delimiter
GetUrlAsyncYesPublic URL or signed URL
StartResumableUploadAsyncYesGCS resumable session
UploadChunkAsyncYesChunk PUT to session URI
CompleteResumableUploadAsyncYesFinal chunk signals completion
AbortResumableUploadAsyncYesDELETE session URI
GetPresignedUploadUrlAsyncYesV4 signed PUT URL
GetPresignedDownloadUrlAsyncYesV4 signed GET URL