On this page

Skip to content

A Practical Discussion on File Upload Handling in ASP.NET Core Web API

TLDR

  • It is recommended to adopt an architecture that "decouples file uploads from business logic," treating file uploads as a standalone API and passing only the file ID within business data.
  • Use [FromForm] for file uploads and [FromBody] for JSON business data to avoid the complexity of implementing ModelBinder when mixing the two.
  • Once file uploads are decoupled, it is recommended to use scheduled tasks to clean up unused temporary files to prevent server storage waste.
  • File downloads should be integrated into business logic Controllers to facilitate fine-grained permission control.
  • For file validation, it is recommended to handle it at the Service layer to avoid direct database dependencies within ValidationAttribute on Request Inputs.

Analysis of File Upload Handling Patterns

In ASP.NET Core Web API, common file handling methods are primarily divided into form submission and Base64 string transmission.

Using Form Submit or FormData

When to use: When developers want to maintain traditional form submission methods, or when files are too large to be converted into strings.

  • Pros: Does not increase data transmission volume.
  • Cons:
    • Having both [FromBody] and [FromForm] in the system leads to inconsistent processing logic between ModelBinder and JsonConverter, increasing maintenance costs.
    • Significant impact on the frontend; if the API originally only supported JSON, adding file functionality requires major adjustments to the frontend transmission format.

Using JSON to Pass Base64 Strings

When to use: When both frontend and backend want to use JSON format for data exchange, and file sizes are relatively small.

  • Pros: Consistent processing logic for both frontend and backend, with a unified API format.
  • Cons: Data size increases by approximately 1.33 times, which may cause performance bottlenecks.

To balance performance and API consistency, it is recommended to separate file uploads from business logic.

Implementation Workflow

  1. File Upload API: Use [FromForm] to receive the file, store it on the file server, write to the file database table, and return the file ID.
  2. Business Data API: When adding or modifying data, pass only the file ID instead of the file content.
  3. Data Management Mechanism:
    • When business data is created, mark the file status as "Enabled."
    • Set up a scheduled task to periodically clean up files and their database records that have remained "Disabled" for more than a day.
    • When deleting business data, you can choose to delete the associated files or mark them as "Disabled."

File Validation Implementation

If you need to access the database during validation, you can obtain the Service via validationContext.

csharp
protected override ValidationResult IsValid(
    object value, ValidationContext validationContext
) {
    using IServiceScope scope = validationContext.CreateScope();
    IFileService service = scope.ServiceProvider.GetRequiredService<IFileService>();
    // Execute validation logic
}

WARNING

File type validation has limitations. Aside from .exe files, most file types are difficult to identify completely through signatures and are prone to false positives. It is recommended to move complex validation logic involving the database to the Action or Service layer.

Considerations for File Download Implementation

After decoupling file uploads, you can choose between a generic API or integration with business logic for download functionality.

  • Generic File Download API: Simple to implement, but difficult to implement fine-grained permission control for different business scenarios.
  • Business Logic Controller Download: This approach is recommended. Although each module needs to write its own download logic, it ensures the rigor of permission control.

TIP

To reduce code duplication, you can create a base file download service class to encapsulate common download logic, which can then be called by various business modules via Dependency Injection (DI).

Change Log

  • 2025-03-10 Initial version created.