-
-
- Set up the initial project structure for FileShareAPI and FileShareLibrary.
- Created the solution file and added projects for the API and the library.
- Defined the core interfaces and classes in FileShareLibrary, including IContentProvider, StreamInfo, and the start of FileShareContentProvider.
- Started on the StoreAsync method implementation.
-
- Researched file handling in .NET and best practices for asynchronous operations.
- Sketched out the endpoint designs for FileShareAPI.
-
-
-
- Implemented and tested StoreAsync, GetAsync, and DeleteAsync methods in FileShareContentProvider.
-
- Developed the FileShareAPI endpoints for upload, download, and delete operations.
- Conducted initial testing of the API endpoints using Postman.
-
- Added logging and error handling mechanisms to both the API and library.
- Started on the UpdateAsync method.
-
-
-
- Completed UpdateAsync, ExistsAsync, GetBytesAsync, and GetHashAsync methods.
- Expanded API endpoints to include checks for file existence, retrieval of bytes, and hash computation.
-
- Began writing unit tests for FileShareLibrary, covering success and error scenarios for StoreAsync and GetAsync.
-
- Continued unit testing with DeleteAsync, ExistsAsync, and UpdateAsync.
- Researched and applied mock setups using Moq for file I/O operations.
-
-
-
- Finalized all unit tests for FileShareLibrary, ensuring high coverage.
- Conducted comprehensive testing of the FileShareAPI with various file types and sizes.
-
- Documented the API endpoints, describing request and response formats.
- Added inline comments and summaries to important methods and classes in the code.
-
- Compiled the project documentation, detailing setup, configuration, usage, and test cases.
- Reviewed the entire project for any missed bugs or improvements, making minor refinements.
- Prepared the project for deployment or sharing, including cleanup of unused references and final testing.
-
This project, FileShareAPI, is designed to manage file operations such as upload, download, deletion, and updates on a file share system. It leverages an ASP.NET Core Minimal API setup for handling HTTP requests and responses, alongside a library, FileShareLibrary, that encapsulates file operation logic.
- FileShareAPI: A web API that exposes endpoints for file operations.
- FileShareLibrary: A class library that provides the core functionality for file operations.
- FileShareLibrary.Tests: A class library that tests the core functionality for file operations.
- Running the tests:
- Navigate to FileShareLibrary.Tests, open appsettings.json, and change the path in the "FileSharePath" variable.
- Running the API:
- Navigate to FileShareAPI, open appsettings.json, and change the path in the "FileSharePath" variable.
The FileShareAPI component defines HTTP endpoints for uploading, downloading, updating, deleting, checking existence, retrieving bytes, and computing hashes of files. It uses the FileShareLibrary for the implementation of these operations.
The API is configured to handle files up to 50 MB in size, as defined in the ConfigureKestrel server options. The file share path is read from configuration settings, allowing dynamic determination of the file storage location.
POST /upload: Uploads a file to the file share. It expects an IFormFile in the request and uses the StoreAsync method of the IContentProvider to save the file.
GET /download/{fileId}: Downloads a file by its StringKey. It uses the GetAsync method to retrieve the file stream and returns it as an application/octet-stream response.
DELETE /delete/{fileId}: Deletes a file by its StringKey. It calls the DeleteAsync method to remove the file from the file share.
GET /exists/{fileId}: Checks if a file exists by its StringKey. It utilizes the ExistsAsync method to verify the presence of the file.
PUT /update/{fileId}: Updates an existing file with new content. This endpoint expects an IFormFile and uses the UpdateAsync method to overwrite the existing file.
GET /bytes/{fileId}: Retrieves the byte array of a file. This endpoint leverages the GetBytesAsync method to fetch the file's bytes.
GET /hash/{fileId}: Computes and returns the SHA-256 hash of a file. It utilizes the GetHashAsync method to generate the hash.
Each endpoint is configured to disable antiforgery token validation and allow anonymous access for simplicity. Depending on the application's security requirements, these settings can be adjusted.
The FileShareLibrary provides the implementation of file operations. It defines the IContentProvider interface and its implementation, FileShareContentProvider, to perform actions on files identified by StringKeys.
The primary purpose of introducing StringKey is to enhance the clarity and manageability of file operations by using a more descriptive identifier than a Guid. A Guid is a 128-bit integer used as a unique identifier, which, while excellent for ensuring uniqueness, does not convey any meaningful information about the file it represents. In contrast, a StringKey could include the file's name and possibly its extension, making it easier for developers and systems to identify and manage files based on their names directly.
- Immutability: As a struct, StringKey is a value type, and this example makes it immutable. Once created with a file name and extension, it cannot be changed. This immutability is beneficial for use as a key in dictionaries or other collections that rely on the consistency of hash codes.
- Meaningful Identification: By incorporating the file's name and extension into the key directly, StringKey allows for more readable and understandable code when dealing with file operations. It can improve logging, error messages, and debugging by providing more context than a Guid.
- Flexibility: Additional functionality can be easily added to the StringKey struct, such as validation, formatting methods, or conversion helpers, to further ease working with file identifiers.
This class implements the IContentProvider interface, handling the logic for file storage, retrieval, updating, and deletion. It determines file extensions based on file content, supporting various file types, and performs operations in a designated file share path.
Stores a file in the specified file share path. It generates a unique file name using the provided id and determines the file extension based on the content's signature to support various file types correctly. This method ensures files are stored securely and reliably, even in the face of network or system failures, by leveraging .NET's file stream capabilities.
- id: A StringKey serving as a unique identifier for the file.
- fileContent: A StreamInfo object containing the file's stream and length.
- cancellationToken: A CancellationToken for handling request cancellations.
- Returns: An OperationResult indicating the success or failure of the operation, including error messages if applicable.
Retrieves a file's stream and information for downloading by matching the id with files in the storage path. This method is optimized to handle large files efficiently, minimizing memory usage by streaming file contents directly from disk to the network.
- id: The unique identifier of the file to retrieve.
- cancellationToken: A token for canceling the operation if necessary.
- Returns: An OperationResult containing the file's stream and size if successful, or error information if not.
Updates an existing file with new content. It first checks for the file's existence by id and then overwrites it with the new content provided in fileContent. This method is critical for maintaining the integrity and up-to-dateness of files in the system.
- id: The StringKey identifying the file to update.
- fileContent: The new content to write to the file, encapsulated in a StreamInfo object.
- cancellationToken: Allows the operation to be cancelled.
- Returns: An OperationResult indicating the outcome of the update operation.
Removes a file from the file share. It searches for the file by id and deletes it if found. This method ensures that all traces of the file are removed from the system, freeing up space and maintaining the cleanliness of the storage area.
- id: The StringKey of the file to delete.
- cancellationToken: A token that can be used to request cancellation of the operation.
- Returns: An OperationResult indicating whether the deletion was successful or if errors occurred.
Checks if a file exists in the file share. By searching for a file with the given id, this method quickly determines file presence, which is essential for validation checks before attempting to access or manipulate files.
- id: The unique identifier of the file to check.
- cancellationToken: A cancellation token for the operation.
- Returns: An OperationResult indicating the existence of the file.
Computes and returns the SHA-256 hash of a file. This method is vital for verifying the integrity of files and ensuring they have not been tampered with. It reads the file stream associated with the id, computes its hash, and returns the hash value as a string.
- id: The unique identifier of the file to hash.
- cancellationToken: A token for cancelling the operation.
- Returns: An OperationResult containing the file's hash value or an error if the file does not exist or an exception occurs.
A support class that encapsulates file stream information, including its length and the stream itself.
A generic class used throughout the library to encapsulate the outcome of operations, including success or failure, error messages, and the result object for successful operations.
This component contains unit tests for the FileShareLibrary. It tests the functionality of file operations, including success and failure scenarios, using mock data and verifying the expected outcomes.
Tests are configured with a mock file share path and use the Moq library to mock dependencies. They cover various scenarios, including file existence, file size limitations, and error handling.
-
- Objective: This test ensures the StoreAsync method can successfully store a file when given valid input.
- Methodology: It mocks a StreamInfo object representing the file to be stored and invokes StoreAsync with a new StringKey. The test verifies if the operation is marked as successful.
- Verification: Asserts that the Success property of the returned OperationResult is true, indicating the operation succeeded without issues.
-
- Objective: Tests whether StoreAsync properly responds to cancellation requests by throwing an OperationCanceledException.
- Methodology: A CancellationToken is canceled before invoking StoreAsync. The method is expected to respect this token and abort the operation.
- Verification: Checks if an OperationCanceledException is thrown, confirming the operation's responsiveness to cancellation.
-
- Objective: Verifies that UpdateAsync correctly handles attempts to update a non-existent file by returning an error.
- Methodology: Attempts to update a file using a StringKey that does not correspond to any existing file. It checks the method's response for error messages.
- Verification: Asserts that the Success flag is false and the Errors collection contains relevant error messages.
-
- Objective: Ensures that GetAsync can retrieve an existing file and return its StreamInfo.
- Methodology: A file is created temporarily, and GetAsync is used to fetch it. The presence and correctness of the returned StreamInfo are then verified.
- Verification: Confirms that Success is true, ResultObject is not null, and the stream within ResultObject matches the content of the file created for the test.
-
- Objective: Tests the ability of GetBytesAsync to handle large files correctly by returning a byte array of the correct length.
- Methodology: Creates a large file and uses GetBytesAsync to retrieve it. The size of the returned byte array is compared to the expected file size.
- Verification: Asserts that the length of the returned byte array matches the size of the file created, ensuring the file's content is correctly read and returned.
-
- Objective: Confirms that GetAsync responds with an appropriate error when requested to retrieve a file that does not exist.
- Methodology: Invokes GetAsync with a StringKey for a non-existent file and examines the response for error indications.
- Verification: Checks that Success is false and that the errors indicate the file could not be found.
-
- Objective: Assesses how GetBytesAsync deals with extremely large files, either by handling gracefully or returning an error.
- Methodology: An exceedingly large file is created and GetBytesAsync is tasked with retrieving it. The method's ability to handle or report errors regarding the file size is observed.
- Verification: Verifies either a successful operation (with correct file size) or the presence of expected error messages related to file size limitations.
-
- Objective: Tests UpdateAsync's behavior under conditions of simultaneous access, ensuring it can successfully update a file.
- Methodology: After creating a file, UpdateAsync is called to modify its contents. The operation's success is then evaluated.
- Verification: Asserts the operation's success, indicating that UpdateAsync can handle simultaneous access scenarios effectively.
-
- Objective: Verifies that GetBytesAsync honors cancellation requests by aborting the operation and throwing OperationCanceledException.
- Methodology: A cancellation token is set to the canceled state before calling GetBytesAsync. The operation's response to this cancellation is then tested.
- Verification: Ensures that an OperationCanceledException is thrown, demonstrating proper cancellation behavior.
-
- Objective: Checks if ExistsAsync accurately determines the existence of a file.
- Methodology: After creating a file, ExistsAsync is used to check for its presence based on its StringKey.
- Verification: Asserts that the result indicates the file exists, validating the method's ability to detect existing files.
-
- Objective: Ensures that the UpdateAsync method properly responds to a cancellation request by aborting the update operation.
- Methodology: Prepares a CancellationToken that is already cancelled and then invokes UpdateAsync with this token. The method should recognize the cancellation and halt the operation.
- Verification: Confirms the method throws an OperationCanceledException, demonstrating it correctly responds to cancellation requests.
-
- Objective: Verifies that UpdateAsync can successfully update the contents of an existing file.
- Methodology: A file is initially created with predefined content. UpdateAsync is then used with new content for this file. Afterward, it verifies if the file content has been updated as expected.
- Verification: Asserts the success of the operation and optionally checks if the file content matches the new content provided to UpdateAsync.
-
- Objective: Tests whether the ExistsAsync method correctly handles a cancellation signal by throwing an OperationCanceledException.
- Methodology: Invokes ExistsAsync with a CancellationToken that has been cancelled, expecting the method to abort and indicate the operation was cancelled.
- Verification: Checks for an OperationCanceledException to be thrown, signifying the method's appropriate reaction to the cancellation request.
-
- Objective: Checks how the StoreAsync method deals with invalid input, specifically null or empty streams.
- Methodology: Calls StoreAsync with a StreamInfo object that has a null Stream property, simulating an invalid input scenario.
- Verification: Asserts that the method returns a failure (Success is false) and contains error messages indicating the invalid input.
-
- Objective: Ensures GetAsync respects cancellation requests by aborting the retrieval operation.
- Methodology: A cancelled CancellationToken is passed to GetAsync. The operation is expected to recognize this cancellation and not proceed.
- Verification: Confirms that an OperationCanceledException is thrown, indicating the operation was cancelled as requested.
-
- Objective: Ensures ExistsAsync accurately reports the non-existence of a file.
- Methodology: ExistsAsync is invoked with a StringKey that does not match any existing file in the system. The method should then return a result indicating the file does not exist.
- Verification: Verifies that the operation result indicates the file does not exist (ResultObject is false) and the operation itself is considered unsuccessful (Success is false).
-
- Objective: Tests the DeleteAsync method's ability to successfully delete an existing file.
- Methodology: A file is first created, then DeleteAsync is called to delete it. The absence of the file is checked afterward.
- Verification: Ensures the operation is successful and the file no longer exists, demonstrating effective file deletion capabilities.
-
- Objective: Checks DeleteAsync's error handling when attempting to delete a file that doesn't exist.
- Methodology: Attempts to delete a file using a StringKey for a non-existent file and examines the response for appropriate error handling.
- Verification: Asserts the operation was not successful (Success is false) and verifies the presence of error messages indicating the file could not be found.
-
- Objective: Ensures DeleteAsync responds correctly to cancellation requests by aborting the delete operation.
- Methodology: A CancellationToken is cancelled prior to invoking DeleteAsync. The method should halt and indicate the operation was cancelled.
- Verification: Checks for an OperationCanceledException, validating the method's adherence to cancellation requests.
-
- Objective: Verifies that GetHashAsync can successfully compute and return the hash of an existing file.
- Methodology: After creating a file with known content, GetHashAsync is used to compute its hash. The hash result is then compared to an expected value or simply checked for existence.
- Verification: Asserts the operation's success and that the result object contains a valid hash string, confirming the method's functionality.
-
- Objective: Tests GetHashAsync's response when attempting to compute the hash of a non-existent file.
- Methodology: Invokes GetHashAsync with a StringKey for a file that does not exist, expecting the
- Verification: Ensures the operation returns a failure (Success is false) and the errors convey that the file could not be found, verifying appropriate error handling for absent files.
-
- Objective: Confirms that GetHashAsync correctly aborts its process when a cancellation request is received, signifying responsive and controlled termination of the operation.
- Methodology: A cancellation token that is already cancelled is passed to the method. GetHashAsync is expected to immediately recognize the cancellation and terminate its execution.
- Verification: Asserts that an OperationCanceledException is thrown, proving the method's proper reaction to the cancellation signal, thus preventing unnecessary processing.
Each test is crafted to simulate specific scenarios that the FileShareLibrary methods might encounter during runtime. This includes handling valid cases (where operations should succeed), dealing with incorrect inputs or states (like non-existent files or invalid data), and responding to external signals such as cancellation requests. The tests make use of the Moq library to mock external dependencies, allowing for isolated testing of the library's logic without interference from the file system or other components.
- Success Condition Checks: Most tests verify the operation's success by inspecting the Success property of the returned OperationResult. Success here means the operation was completed as intended without encountering unforeseen issues.
- Error Handling Checks: For tests involving error scenarios, the verification involves ensuring the Success property is false and that the Errors collection within the OperationResult contains appropriate messages that accurately describe the encountered issue.
- Exception Handling Checks: For tests that involve operation cancellation, the expected outcome is the throwing of an OperationCanceledException. This exception indicates that the method respected the cancellation token and ceased its operation promptly.