file-handling
File uploads, streaming, storage abstractions, and secure file handling patterns for ASP.NET Core Razor Pages applications. Use when implementing secure file uploads in Razor Pages, streaming large files, or creating storage abstractions for file operations.
What this skill does
## Rationale
File handling is a common requirement but presents significant security and scalability challenges. Improper implementation can lead to security vulnerabilities (path traversal, malicious uploads), memory exhaustion, and storage inefficiencies. These patterns provide secure, performant, and maintainable approaches to file operations in Razor Pages.
## Patterns
### Pattern 1: Secure File Upload Validation
Implement comprehensive validation for file uploads including type, size, and content verification.
```csharp
public class FileUploadValidator
{
private readonly long _maxFileSize;
private readonly string[] _allowedExtensions;
private readonly Dictionary<string, byte[]> _fileSignatures;
public FileUploadValidator(IConfiguration configuration)
{
_maxFileSize = configuration.GetValue<long>("FileUpload:MaxSize", 10 * 1024 * 1024); // 10MB
_allowedExtensions = configuration.GetSection("FileUpload:AllowedExtensions")
.Get<string[]>() ?? new[] { ".jpg", ".jpeg", ".png", ".pdf", ".doc", ".docx" };
// Magic numbers for file type validation
_fileSignatures = new Dictionary<string, byte[]>(StringComparer.OrdinalIgnoreCase)
{
[".jpg"] = new byte[] { 0xFF, 0xD8, 0xFF },
[".jpeg"] = new byte[] { 0xFF, 0xD8, 0xFF },
[".png"] = new byte[] { 0x89, 0x50, 0x4E, 0x47 },
[".pdf"] = new byte[] { 0x25, 0x50, 0x44, 0x46 },
[".docx"] = new byte[] { 0x50, 0x4B, 0x03, 0x04 }
};
}
public async Task<ValidationResult> ValidateAsync(IFormFile file)
{
// Check file exists
if (file == null || file.Length == 0)
{
return ValidationResult.Failure("No file provided");
}
// Check file size
if (file.Length > _maxFileSize)
{
return ValidationResult.Failure(
$"File size exceeds maximum allowed size of {_maxFileSize / 1024 / 1024}MB");
}
// Get and validate extension
var extension = Path.GetExtension(file.FileName).ToLowerInvariant();
if (!_allowedExtensions.Contains(extension))
{
return ValidationResult.Failure(
$"File type '{extension}' is not allowed. Allowed types: {string.Join(", ", _allowedExtensions)}");
}
// Validate file signature (magic number)
if (_fileSignatures.TryGetValue(extension, out var signature))
{
using var stream = file.OpenReadStream();
var header = new byte[signature.Length];
var bytesRead = await stream.ReadAsync(header, 0, signature.Length);
if (bytesRead < signature.Length || !header.SequenceEqual(signature))
{
return ValidationResult.Failure(
"File content does not match the declared file type");
}
}
// Reset stream position if needed later
if (file is { Position: > 0 })
{
file.Position = 0;
}
return ValidationResult.Success();
}
}
public record ValidationResult(bool IsValid, string? ErrorMessage)
{
public static ValidationResult Success() => new(true, null);
public static ValidationResult Failure(string error) => new(false, error);
}
```
### Pattern 2: Streaming Large Files
Handle large file uploads efficiently without loading entire files into memory.
```csharp
public class StreamingFileUploadModel : PageModel
{
private readonly IFileStorageService _storage;
private readonly FileUploadValidator _validator;
private readonly ILogger<StreamingFileUploadModel> _logger;
public StreamingFileUploadModel(
IFileStorageService storage,
FileUploadValidator validator,
ILogger<StreamingFileUploadModel> logger)
{
_storage = storage;
_validator = validator;
_logger = logger;
}
[BindProperty]
public string? Description { get; set; }
public string? ErrorMessage { get; set; }
public string? SuccessMessage { get; set; }
// Disable form value limit for streaming
[RequestSizeLimit(500 * 1024 * 1024)] // 500MB
[RequestFormLimits(ValueLengthLimit = int.MaxValue, MultipartBodyLengthLimit = 500 * 1024 * 1024)]
public async Task<IActionResult> OnPostAsync()
{
if (!MultipartRequestHelper.IsMultipartContentType(Request.ContentType))
{
ErrorMessage = "Invalid content type";
return Page();
}
var boundary = MultipartRequestHelper.GetBoundary(
MediaTypeHeaderValue.Parse(Request.ContentType),
int.MaxValue);
var reader = new MultipartReader(boundary, HttpContext.Request.Body);
var section = await reader.ReadNextSectionAsync();
while (section != null)
{
if (ContentDispositionHeaderValue.TryParse(
section.ContentDisposition, out var contentDisposition))
{
if (MultipartRequestHelper.HasFileContentDisposition(contentDisposition))
{
var fileName = contentDisposition.FileName.Value?.Trim('"') ?? "unnamed";
var safeFileName = Path.GetFileName(fileName); // Prevent path traversal
// Validate file type by extension
var extension = Path.GetExtension(safeFileName).ToLowerInvariant();
var allowedExtensions = new[] { ".pdf", ".doc", ".docx" };
if (!allowedExtensions.Contains(extension))
{
ErrorMessage = $"File type '{extension}' not allowed";
return Page();
}
// Stream directly to storage without loading into memory
var fileId = await _storage.UploadStreamAsync(
section.Body,
safeFileName,
section.ContentType ?? "application/octet-stream");
SuccessMessage = $"File uploaded successfully with ID: {fileId}";
// Log upload
_logger.LogInformation(
"File uploaded: {FileName} with ID {FileId} by user {User}",
safeFileName, fileId, User.Identity?.Name ?? "anonymous");
}
}
section = await reader.ReadNextSectionAsync();
}
return Page();
}
}
// Helper class
public static class MultipartRequestHelper
{
public static string GetBoundary(MediaTypeHeaderValue contentType, int lengthLimit)
{
var boundary = HeaderUtilities.RemoveQuotes(contentType.Boundary).Value;
if (string.IsNullOrWhiteSpace(boundary))
{
throw new InvalidDataException("Missing content-type boundary");
}
if (boundary.Length > lengthLimit)
{
throw new InvalidDataException(
$"Multipart boundary length limit {lengthLimit} exceeded");
}
return boundary;
}
public static bool IsMultipartContentType(string? contentType) =>
!string.IsNullOrEmpty(contentType) &&
contentType.Contains("multipart/", StringComparison.OrdinalIgnoreCase);
public static bool HasFormDataContentDisposition(ContentDispositionHeaderValue contentDisposition) =>
contentDisposition != null &&
contentDisposition.DispositionType.Equals("form-data") &&
string.IsNullOrEmpty(contentDisposition.FileName.Value) &&
string.IsNullOrEmpty(contentDisposition.FileNameStar.Value);
public static bool HasFileContentDisposition(ContentDispositionHeaderValue contentDisposition) =>
contentDisposition != null &&
contentDisposition.DispositionType.Equals("form-data") &&
(!string.IsNullOrRelated in General
modeling-omnistudio-epc-catalog
IncludedSalesforce Industries CME EPC product-modeling skill for Product2-based catalog creation. Use when creating EPC products, configuring product attributes, building offer bundles with Product Child Items, or reviewing EPC DataPack JSON metadata for product catalog changes. TRIGGER when: user creates or updates Product2 EPC records, AttributeAssignment payloads, AttributeMetadata/AttributeDefaultValues, Offer bundles, or ProductChildItem relationships. DO NOT TRIGGER when: designing OmniScripts/FlexCards/Integration Procedures (use building-omnistudio-omniscript, building-omnistudio-flexcard, or building-omnistudio-integration-procedure), implementing Apex business logic (use generating-apex), or troubleshooting deployment pipelines (use deploying-metadata).
relationship-science-coach
IncludedUse this skill for direct, practical adult relationship coaching: couples conflict, repair, trust, marriage, dating, flirting, attachment patterns, emotional connection, sex, desire differences, eroticism, kink negotiation, affection, love languages, breakups, and long-term passion. Draw on Gottman, EFT and Hold Me Tight, attachment science, modern sex research, Perel, Nagoski, Kerner, Schnarch, Love and Stosny, and flexible love-language tools. Be concrete and low-hedge. Redirect only for imminent danger, abuse, coercive control, minors, non-consent, self-harm, stalking, or medical/legal/psychiatric decisions.
building-sf-integrations
IncludedSalesforce integration architecture and runtime plumbing with 120-point scoring. Use this skill to set up Named Credentials, External Credentials, External Services, REST/SOAP callout patterns, Platform Events, and Change Data Capture. TRIGGER when: user sets up Named Credentials, External Services, REST/SOAP callouts, Platform Events, CDC, or touches .namedCredential-meta.xml files. DO NOT TRIGGER when: Connected App/OAuth config (use configuring-connected-apps), Apex-only logic (use generating-apex), or data import/export (use handling-sf-data).
venue-templates
IncludedAccess comprehensive LaTeX templates, formatting requirements, and submission guidelines for major scientific publication venues (Nature, Science, PLOS, IEEE, ACM), academic conferences (NeurIPS, ICML, CVPR, CHI), research posters, and grant proposals (NSF, NIH, DOE, DARPA). This skill should be used when preparing manuscripts for journal submission, conference papers, research posters, or grant proposals and need venue-specific formatting requirements and templates.
let-fate-decide
IncludedDraws the 12 Houses of the Zodiac Tarot spread to inject entropy into planning when prompts are vague, ambiguous, or casually delegated. Interprets the spread to guide next steps. Use when the user says 'let fate decide', 'YOLO', 'whatever', 'idk', or other nonchalant phrases, makes Yu-Gi-Oh references, or when you are about to arbitrarily pick between multiple reasonable approaches. Prefer over ask-questions-if-underspecified when the user's tone is casual or playful rather than precision-seeking.
net-ops
IncludedCross-platform network troubleshooting (Windows, macOS, Linux) via local or remote shell. Use for: DNS broken, can't resolve hostnames, nslookup/dig works but apps fail, NRPT, WFP, scutil, /etc/resolver, systemd-resolved, /etc/resolv.conf, NetworkManager, VPN DNS leak residue (ProtonVPN/Mullvad/WireGuard/AnyConnect), AV/firewall blocking DNS or DoH, Tailscale DNS interaction, intermittent connectivity, remote diagnostics over SSH.