LargeFilesManager is a modern .NET 8 solution designed to efficiently manage, process, and transfer large files in enterprise environments. The architecture emphasizes scalability, maintainability, and testability, leveraging best practices and industry standards.
Solution include 2 applications:
- File Generator (LFM.FileGenerator): Creates large text files efficiently.
- File Sorter (LFM.FileParser): Sorts very large files deterministically without loading the whole file into memory.
Both apps are designed to process large files with parallelism, streaming I/O, and consistent progress reporting.
File Generator:
- Choose output folder, file name, target size (B/KB/MB/GB).
- Start generation; progress bar and logs indicate status.
File Sorter:
- Choose input file and output file path.
- Start sorting; the app splits, sorts parts, and merges to output.
- Final output is sorted by text then number, matching the generator’s semantics.
- Layered Architecture: The solution is organized into distinct layers (Presentation, Application, Domain, Infrastructure, and Testing), promoting separation of concerns and easy extensibility.
- Dependency Injection: Utilizes .NET’s built-in DI for loose coupling and testability.
- Repository & Unit of Work Patterns: Abstracts data access and transaction management.
- CQRS (Command Query Responsibility Segregation): Segregates read and write operations for performance and clarity.
- Asynchronous Programming: Uses async/await for scalable I/O operations.
LargeFilesManager/
│
├── LargeFilesManager.sln
├── src/
│ ├── LargeFilesManager.WebApi/ # ASP.NET Core Web API (Presentation Layer)
│ ├── LargeFilesManager.Application/ # Application Layer (CQRS, Services)
│ ├── LargeFilesManager.Domain/ # Domain Layer (Entities, Interfaces)
│ ├── LargeFilesManager.Infrastructure/# Infrastructure Layer (Data, File Storage, External Integrations)
│
├── tests/
│ ├── LargeFilesManager.UnitTests/ # Unit Tests (xUnit/NUnit/MSTest)
│ ├── LargeFilesManager.IntegrationTests/ # Integration Tests
│
└── README.md
- .NET 8 (C#).
- WPF.
- Entity Framework Core (for data access).
- SQL Server (default, can be swapped for other providers).
- xUnit/NUnit/MSTest (for testing).
- Swagger/OpenAPI (for API documentation).
- AutoMapper (object mapping).
- Serilog (logging).
- Third-Party Libraries:
-
- AutoMapper.Extensions.Microsoft.DependencyInjection.
-
- Microsoft.EntityFrameworkCore.SqlServer.
-
- Microsoft.EntityFrameworkCore.InMemory (for tests).
-
- Repository Pattern: Encapsulates data access logic.
- Unit of Work Pattern: Manages transactions.
- CQRS: Separates command and query responsibilities.
- Dependency Injection: Promotes testability and modularity.
- Configuration via appsettings.json: Centralized configuration management.
- Logging & Monitoring: Serilog for structured logging.
Lines generated and processed follow the template: .
Example:
- Apple
- Apple
- Banana is yellow
- Cherry is the best
- Something something something
Purpose: Generates large text files quickly, splitting work across part files and merging them into a single output.
Key characteristics:
- Streaming I/O with FileStream + StreamWriter.
- Parallel part generation based on processor count.
- Deterministic line structure; words-only generation for text.
- Accurate, thread-safe progress tracking.
- Explicit UTF-8 (no BOM) encoding for consistency.
- Initialization
- Reset progress panel state: ProgressMinValue, ProgressMaxValue, ProgressValue, ProgressStatus.
- Compute target file size in bytes using ByteHelper.ConvertToBytes(fileSizeType, fileSize).
- Determine buffer size from BufferFileWriteSize with a safe minimum (4 KB).
- Select degree of parallelism based on ProcessorCount.
- Compute sizePerFile = targetSize / parallelParts.
- Delete any existing final file and stale part files.
- Information added:
- Parallel part file creation
- For i in [0..parallelParts):
- Compute part file name: .part_{i+1}. when parallel parts > 1.
- Open a write stream: FileStream(partPath, Create) + StreamWriter(UTF8 no BOM).
- Loop until part reaches sizePerFile:
- Generate [text] using words-only generator:
- Build alphabetic words separated by single spaces; total length <= maxLineLength.
- Increment the global LineNumber atomically.
- Write line with template: "{LineNumber}. {text}".
- Update progress by actual bytes written: encoding byte count of line + newline.
- If the next similar write would exceed sizePerFile, write one last line to approximate target and exit.
- Merge part files into final file
- Open the final output stream once.
- Read each part file line-by-line in parallel.
- Serialize writes to the final writer via a lock.
- Update progress using accurate byte counts on each merged line.
- Log merge completion, delete part files, and mark process complete.
- Process completed state
- Click "Reset Form" button to start new generation process.
Error handling and logging
- All major operations (generation, merging, file deletion) log progress and errors using Serilog.
- Exceptions during merge do not delete part files, enabling retry.
Encoding and counting
- Use UTF-8 without BOM for both writer and reader.
- Calculate progress by encoding-aware byte counts (line + newline) to avoid drift.
Purpose: Sorts very large files according to the format’s semantics, using external sorting:
- Sort by the text portion alphabetically (Ordinal).
- When texts are equal, sort by the numeric prefix ascending.
Key characteristics:
- Split-then-merge pipeline (external sorting).
- Streaming I/O, blocking queues, and parallel consumers.
- Stable, duplicate-preserving k-way merge.
- Accurate, thread-safe progress updates.
- Explicit UTF-8 (no BOM) encoding throughout.
- Initialization
- Reset progress panel state and status.
- Inspect input file size for progress tracking.
- Compute target part size in bytes using MaxPartFileSizeMegaBytes.
- Derive bounded capacity for the internal BlockingCollection to regulate memory/flow.
- Start consumer tasks (writers) based on TotalConsumerTasks.
- Information added:
- Producer: read and split
- Open the input file with StreamReader (UTF-8 no BOM).
- Read line-by-line, calculating byte size per line (text + newline).
- Parse each line to ParsedLine:
- NumericPrefix: integer portion before .
- Text: substring after .
- OriginalLine: original text line
- Accumulate lines until the current part reaches targetPartFileSizeBytes.
- Sort the current part in-memory using ParsedLineComparer:
- Compare by Text (Ordinal).
- If equal, compare by NumericPrefix.
- Add the sorted lines (OriginalLine) to the blocking collection as a PartQueue.
- Repeat until EOF; flush remaining lines as the last part.
- Consumers: write sorted parts
- Each consumer:
- Dequeues PartQueue items.
- Writes sorted lines to a temporary part file in a unique temp directory.
- Updates progress by the resulting part file length.
- Records the part file path and clears queue memory.
- Merge sorted part files
- Initialize readers for each part (UTF-8 no BOM).
- Build a candidate map: SortedDictionary<ParsedLine, Queue> keyed by the next line from each part (parsed).
- Queue maintains file paths producing identical parsed lines; this preserves duplicates.
- Open the final output writer (UTF-8 no BOM).
- While candidates exist:
- Pop the smallest parsed line (Text first, then NumericPrefix).
- Write the OriginalLine to output.
- Update progress with accurate bytes written.
- Dequeue the producing file path and read its next line:
- If available, reinsert into candidates; otherwise, that path is exhausted.
- Dispose readers, report completion, and delete temp part files.
- Process completed state
- Click "Reset Form" button to start new generation process.
- Both split and merge stages update ProgressStatus and ProgressValue.
- Exceptions log context and are allowed to propagate to global handlers (App-level safety net).
- AppSettings (LFM.Core.AppSettings):
- BufferFileWriteSize (KB), TotalConsumerTasks, MaxPartFileSizeMegaBytes, and UI resources.
- Logging:
- Serilog configured in AppStartupHelper.ConfigureLogging(), with console + file sinks.
- Localization:
- StringLocalizer provides UI strings and messages.
- BaseService (LFM.Core.Services.BaseService) manages:
- ProgressMinValue, ProgressMaxValue, ProgressValue, ProgressStatus.
- Dispatcher timer tick to display elapsed time.
- Thread-safe locks for progress and shared counters (e.g., LineNumberLock).
- Streaming I/O with explicit buffer sizes.
- Accurate encoding-aware byte counting for progress.
- Deterministic formatting and sorting semantics.
- Parallelism used where safe (part generation; part writing; merge reads).
- Thread-safe updates via locks around shared state.
- Resource cleanup via using and guarded deletes.
- Error logging with actionable context; failures avoid destructive cleanup when retry is possible.
- Visual Studio 2022+ (with .NET 8 SDK)
- SQL Server (local or remote)
- Clone the Repository
git clone https://github.com/DenisKovalyonokSamples/LargeFilesManager.git-
Open Solution
- Launch Visual Studio.
- Open LargeFilesManager.sln.
-
Configure Database
- Update appsettings.json in LargeFilesManager.WebApi with your SQL Server connection string.
-
Restore NuGet Packages
- Visual Studio will auto-restore packages on build.
-
Build Solution
- Right-click the solution > Build.
-
Apply Migrations
- Open Package Manager Console.
- Select LargeFilesManager.Infrastructure as default project.
- Run:
Update-Database- Run the Web API
- Set LargeFilesManager.WebApi as startup project.
- Press F5 or click "Start Debugging".
- Located in LargeFilesManager.UnitTests.
- Covers core business logic, domain entities, and application services.
- Uses in-memory database for isolation.
-
Open Test Explorer
- Visual Studio: View > Test Explorer.
-
Run All Tests
- Click "Run All" in Test Explorer.
-
Command Line (Optional)
- Navigate to solution directory.
- Run:
dotnet test- Follows SOLID principles and clean code practices.
- All code changes require passing tests and code review.
This project is licensed under the MIT License.










