Implement File Service in your ASP.NET Core/.NET Project(The Right Way)
In our each application, there we can deal with files. Sometimes we write file upload, download, and delete code directly in the Controller. But it is not a good practice. Because in real-world apps, we need to deal with files in multiple places and for multiple purposes—user profile images, report exports, documents, logs, and more.
The best way to handle this is to create common reusable code for all file operations, and that’s where the concept of a FileService comes in.
Imagine writing file upload logic in five different controllers. Then imagine changing how files are stored (say, moving from local storage to AWS S3). Now you have to hunt down every file-related line of code scattered across the app. Sounds like a nightmare, right?
That’s exactly why we need a centralized, reusable, and easily replaceable way of handling files.
FileService
interface and implementation.A well-designed FileService
can:
-
Reduce code repetition
-
Make the app more modular and testable
-
Easily switch storage strategies (local, cloud, etc.)
-
Speed up development and onboarding
In this blog we will serve you step by step details process about FileService
Step 1: Create the File Service Interface
First of all we'll define Interface that contains File Operations like Upload, Download, Delete.
public interface IFileService {
Task<string> UploadFileAsync(IFormFile file, string directoryPath); string GetFilepath(string FilePath); Task<byte[]> DownloadFileAsync(string FilePath); Task<IFormFile> GetIFormFile(string filePath); bool DeleteFile(string filePath);
}
This gives you a flexible abstraction. You can create different implementations for local storage, AWS S3, Azure Blob, or even Google Drive—all without touching your controller logic. That’s the beauty of abstraction.
Step 2: Implement the File Service
Now let’s implement the interface using local file system storage.
public class FileService : IFileService { public async Task<string?> UploadFileAsync(IFormFile file, string? directoryPath) { string filePath = null; if (file != null) { if (!Directory.Exists(directoryPath)) { // Directory does not exist, create it Directory.CreateDirectory(directoryPath); //eg "wwwroot/FileOrDoc/Images" } string fileName = Guid.NewGuid().ToString() + Path.GetExtension(file.FileName); filePath = GetFilepath( Path.Combine(directoryPath, fileName)); var path = Path.Combine(directoryPath, fileName);
using var stream = new FileStream(path, FileMode.Create); await file.CopyToAsync(stream); }
return filePath; } public string? GetFilepath(string FilePath) { int index = FilePath.IndexOf("Images"); string desiredPart = "";
if (index != -1) { // Get the substring starting from the index of "FileOrDoc" directory desiredPart = FilePath.Substring(index + "FileOrDoc".Length);
// Replace backslashes with forward slashes desiredPart = desiredPart.Replace('\\', '/'); desiredPart = "/FileOrDoc" + desiredPart; // Output the result //Console.WriteLine(desiredPart); } return desiredPart; } public async Task<byte[]> DownloadFileAsync(string filePath) {
var fileName = Path.GetFileName(filePath);
byte[] fileBytes = await System.IO.File.ReadAllBytesAsync(filePath); return fileBytes; }
public async Task<IFormFile> GetIFormFile(string filePath) { if (!File.Exists(filePath)) { //throw new FileNotFoundException("File not found", filePath); return null; }
var stream = new FileStream(filePath, FileMode.Open, FileAccess.Read); var formFile = new FormFile(stream, 0, stream.Length, "file", Path.GetFileName(filePath)) { Headers = new HeaderDictionary(), ContentType = GetContentType(filePath) // Optional: Set MIME type };
return formFile; }
// Optional: Get the MIME type based on the file extension private string GetContentType(string filePath) { var provider = new Microsoft.AspNetCore.StaticFiles.FileExtensionContentTypeProvider(); if (provider.TryGetContentType(filePath, out var contentType)) { return contentType; } return "application/octet-stream"; // Default MIME type }
public bool DeleteFile(string filePath) { if (File.Exists(filePath)) { File.Delete(filePath); return true; }
return false; } }
This implementation handles:
-
Uploading with a unique filename
-
Creating directories if they don’t exist
-
Extracting relative file paths for easy URL generation
-
Converting files back to
IFormFile
(super useful when mocking uploads) -
Deleting files safely
Step 3: Register FileService in Program.cs
This makes IFileService
available anywhere in your app using dependency injection.
builder.Services.AddScoped<IFileService, FileService>();
Step 4: Use FileService in Your Controller
we can use this above IFileService either in MVC or RESTful API, But here we used this service in API.
[ApiController] [Route("api/[controller]")] public class FileController : ControllerBase { private readonly IFileService _fileService;
public FileController(IFileService fileService) { _fileService = fileService; }
[HttpPost("upload")] public async Task<IActionResult> UploadFile(IFormFile file) { var savedPath = await _fileService.UploadFileAsync(file, "wwwroot/FileOrDoc/Images"); var fileUrl = _fileService.GetFileUrl(savedPath); return Ok(new { path = savedPath, url = fileUrl }); }
[HttpGet("download")] public async Task<IActionResult> DownloadFile(string path) { var bytes = await _fileService.DownloadFileAsync(path); return File(bytes, "application/octet-stream", Path.GetFileName(path)); }
[HttpDelete("delete")] public IActionResult DeleteFile(string path) { var result = _fileService.DeleteFile(path); return result ? Ok("File deleted successfully.") : NotFound("File not found."); } }
Bonus: Swapping to Cloud Storage Is Easy
One of the biggest advantages of using an interface-based design is future flexibility.
Let’s say you want to store files in AWS S3 tomorrow. You just:
-
Create
S3FileService : IFileService
-
Update the DI container to use
S3FileService
-
Done. No changes to controller code.
This makes your application portable, testable, and future-ready.
Don’t write file upload logic 10 times. Write it once—smartly.
Final Thoughts
Don’t write file upload logic 10 times in 10 different places.
Write it once. Smartly.
By building a central FileService
, you unlock:
-
Better maintainability
-
Reusability
-
Cleaner controllers
-
Easier testing
-
Seamless storage switching
This is what clean architecture is all about—separation of concerns, reusability, and future-proofing.
Final Suggestion for Developers
As backend developers, I'm suggest to every developer please follow these three steps
- First : make it workable
- Second : Refine code
- Third : Make it Portable
we're constantly striving to write cleaner, scalable, and more maintainable code. Managing files might seem like a small concern in the early stages of a project, but as your application grows, scattered file logic can become a real pain point.
So here’s the golden rule: Don’t repeat yourself—centralize your file operations.
A reusable FileService
not only saves time and reduces bugs but also prepares your app for future enhancements like cloud storage, CDN delivery, or image optimization pipelines.
✨ Follow the Syntax make a Rule
Start small, implement the service layer once, and watch how it improves your development flow across the board.
Comments (0)