-
Notifications
You must be signed in to change notification settings - Fork 58
chore: add file manager blob storage example #105
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
2 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
34 changes: 34 additions & 0 deletions
34
KendoUI.FileManager.BlobStorage/KendoUI.FileManager.BlobStorage.sln
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| | ||
| Microsoft Visual Studio Solution File, Format Version 12.00 | ||
| # Visual Studio Version 17 | ||
| VisualStudioVersion = 17.0.31903.59 | ||
| MinimumVisualStudioVersion = 10.0.40219.1 | ||
| Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KendoUI.FileManager.BlobStorage", "KendoUI.FileManager.BlobStorage\KendoUI.FileManager.BlobStorage.csproj", "{8C002550-8D5E-43CE-8F1F-A3A7D5149F48}" | ||
| EndProject | ||
| Global | ||
| GlobalSection(SolutionConfigurationPlatforms) = preSolution | ||
| Debug|Any CPU = Debug|Any CPU | ||
| Debug|x64 = Debug|x64 | ||
| Debug|x86 = Debug|x86 | ||
| Release|Any CPU = Release|Any CPU | ||
| Release|x64 = Release|x64 | ||
| Release|x86 = Release|x86 | ||
| EndGlobalSection | ||
| GlobalSection(ProjectConfigurationPlatforms) = postSolution | ||
| {8C002550-8D5E-43CE-8F1F-A3A7D5149F48}.Debug|Any CPU.ActiveCfg = Debug|Any CPU | ||
| {8C002550-8D5E-43CE-8F1F-A3A7D5149F48}.Debug|Any CPU.Build.0 = Debug|Any CPU | ||
| {8C002550-8D5E-43CE-8F1F-A3A7D5149F48}.Debug|x64.ActiveCfg = Debug|Any CPU | ||
| {8C002550-8D5E-43CE-8F1F-A3A7D5149F48}.Debug|x64.Build.0 = Debug|Any CPU | ||
| {8C002550-8D5E-43CE-8F1F-A3A7D5149F48}.Debug|x86.ActiveCfg = Debug|Any CPU | ||
| {8C002550-8D5E-43CE-8F1F-A3A7D5149F48}.Debug|x86.Build.0 = Debug|Any CPU | ||
| {8C002550-8D5E-43CE-8F1F-A3A7D5149F48}.Release|Any CPU.ActiveCfg = Release|Any CPU | ||
| {8C002550-8D5E-43CE-8F1F-A3A7D5149F48}.Release|Any CPU.Build.0 = Release|Any CPU | ||
| {8C002550-8D5E-43CE-8F1F-A3A7D5149F48}.Release|x64.ActiveCfg = Release|Any CPU | ||
| {8C002550-8D5E-43CE-8F1F-A3A7D5149F48}.Release|x64.Build.0 = Release|Any CPU | ||
| {8C002550-8D5E-43CE-8F1F-A3A7D5149F48}.Release|x86.ActiveCfg = Release|Any CPU | ||
| {8C002550-8D5E-43CE-8F1F-A3A7D5149F48}.Release|x86.Build.0 = Release|Any CPU | ||
| EndGlobalSection | ||
| GlobalSection(SolutionProperties) = preSolution | ||
| HideSolutionNode = FALSE | ||
| EndGlobalSection | ||
| EndGlobal |
218 changes: 218 additions & 0 deletions
218
...oUI.FileManager.BlobStorage/KendoUI.FileManager.BlobStorage/Controllers/HomeController.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,218 @@ | ||
| using KendoUI.FileManager.BlobStorage.Models; | ||
| using Microsoft.AspNetCore.Mvc; | ||
| using KendoUI.FileManager.BlobStorage.Services; | ||
| using System.Diagnostics; | ||
| using System.Text.Json; | ||
|
|
||
| namespace KendoUI.FileManager.BlobStorage.Controllers | ||
| { | ||
| public class HomeController : Controller | ||
| { | ||
| private readonly ILogger<HomeController> _logger; | ||
| // Inject the service that handles Azure Blob Storage operations | ||
| private readonly IBlobFileManagerService _fileManagerService; | ||
|
|
||
| public HomeController(ILogger<HomeController> logger, IBlobFileManagerService fileManagerService) | ||
| { | ||
| _logger = logger; | ||
| _fileManagerService = fileManagerService; | ||
| } | ||
|
|
||
| public IActionResult Index() | ||
| { | ||
| return View(); | ||
| } | ||
|
|
||
| public IActionResult Alternative() | ||
| { | ||
| return View("Index_Alternative"); | ||
| } | ||
|
|
||
| public IActionResult About() | ||
| { | ||
| ViewData["Message"] = "Your application description page."; | ||
| return View(); | ||
| } | ||
|
|
||
| public IActionResult Contact() | ||
| { | ||
| ViewData["Message"] = "Your contact page."; | ||
| return View(); | ||
| } | ||
|
|
||
| public IActionResult Error() | ||
| { | ||
| return View(); | ||
| } | ||
|
|
||
| // Handles reading files and folders from Azure Blob Storage for the FileManager | ||
| [HttpPost] | ||
| public async Task<IActionResult> FileManager_Read([FromForm] FileManagerReadRequest request) | ||
| { | ||
| try | ||
| { | ||
| // Retrieve the list of blobs/folders from the specified path | ||
| var target = NormalizePath(request.Target ?? request.Path ?? string.Empty); | ||
| var files = await _fileManagerService.ReadAsync(target); | ||
| return Json(files); | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| _logger.LogError(ex, "Failed to read FileManager contents."); | ||
| return BadRequest(new { error = ex.Message }); | ||
| } | ||
| } | ||
|
|
||
| // Handles creating new folders or copying/pasting files in Azure Blob Storage | ||
| [HttpPost] | ||
| public async Task<IActionResult> FileManager_Create([FromForm] FileManagerCreateRequest request) | ||
| { | ||
| try | ||
| { | ||
| var target = NormalizePath(request.Target ?? | ||
| request.Path ?? | ||
| request.Source ?? | ||
| request.SourcePath ?? | ||
| string.Empty); | ||
| var name = request.Name?.Trim(); | ||
| var entry = request.Entry; | ||
|
|
||
| if (string.IsNullOrWhiteSpace(name)) | ||
| { | ||
| return BadRequest(new { error = "Name is required for create operation" }); | ||
| } | ||
|
|
||
| // Parse the form data to determine if this is a folder creation, file upload, or copy operation | ||
| var context = FileManagerCreateContext.FromRequest(request); | ||
| var result = await _fileManagerService.CreateAsync(target, name, entry, context); | ||
| return Json(result); | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| _logger.LogError(ex, "Failed to create entry in FileManager."); | ||
| return BadRequest(new { error = ex.Message }); | ||
| } | ||
| } | ||
|
|
||
| // Handles renaming files or folders in Azure Blob Storage | ||
| [HttpPost] | ||
| public async Task<IActionResult> FileManager_Update([FromForm] FileManagerUpdateRequest request) | ||
| { | ||
| try | ||
| { | ||
| var targetPath = request.Path; | ||
| var newName = request.Name; | ||
|
|
||
| if (string.IsNullOrEmpty(targetPath) || string.IsNullOrEmpty(newName)) | ||
| { | ||
| return BadRequest(new { error = "Path and name are required for rename operation" }); | ||
| } | ||
|
|
||
| // Rename is implemented by copying the blob to a new path and deleting the old one | ||
| var result = await _fileManagerService.UpdateAsync(targetPath!, newName!); | ||
| return Json(result); | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| _logger.LogError(ex, "Failed to rename FileManager entry."); | ||
| return BadRequest(new { error = ex.Message }); | ||
| } | ||
| } | ||
|
|
||
| // Handles deleting files or folders from Azure Blob Storage | ||
| [HttpPost] | ||
| public async Task<IActionResult> FileManager_Destroy([FromForm] FileManagerDestroyRequest request) | ||
| { | ||
| try | ||
| { | ||
| // Parse the request to extract the path of the item to delete | ||
| var targetPath = ResolveTargetPath(request); | ||
| if (string.IsNullOrEmpty(targetPath)) | ||
| { | ||
| return BadRequest(new { error = "No target path provided for deletion." }); | ||
| } | ||
|
|
||
| await _fileManagerService.DeleteAsync(targetPath); | ||
| return Json(Array.Empty<object>()); | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| _logger.LogError(ex, "Failed to delete FileManager entry."); | ||
| return BadRequest(new { error = ex.Message }); | ||
| } | ||
| } | ||
|
|
||
| // Handles file uploads to Azure Blob Storage | ||
| [HttpPost] | ||
| public async Task<IActionResult> FileManager_Upload([FromForm] FileManagerUploadRequest request) | ||
| { | ||
| try | ||
| { | ||
| if (request.File == null || request.File.Length == 0) | ||
| { | ||
| return BadRequest(new { error = "No file uploaded" }); | ||
| } | ||
|
|
||
| // Normalize the target path and upload the file to the blob container | ||
| var resolvedTarget = NormalizeUploadTarget(request); | ||
| var result = await _fileManagerService.UploadAsync(resolvedTarget, request.File); | ||
| return Json(result); | ||
| } | ||
| catch (Exception ex) | ||
| { | ||
| _logger.LogError(ex, "Failed to upload file."); | ||
| return BadRequest(new { error = ex.Message }); | ||
| } | ||
| } | ||
|
|
||
| private static string NormalizePath(string? target) | ||
| { | ||
| if (string.IsNullOrWhiteSpace(target)) | ||
| { | ||
| return string.Empty; | ||
| } | ||
|
|
||
| return target.Trim('/'); | ||
| } | ||
|
|
||
| private static string NormalizeUploadTarget(FileManagerUploadRequest request) | ||
| { | ||
| var resolvedTarget = request.Target ?? request.Path ?? string.Empty; | ||
| return NormalizePath(resolvedTarget); | ||
| } | ||
|
|
||
| private static string? ResolveTargetPath(FileManagerDestroyRequest request) | ||
| { | ||
| var targetPath = request.Path ?? request.Target ?? request.Name; | ||
|
|
||
| if (!string.IsNullOrWhiteSpace(targetPath)) | ||
| { | ||
| return targetPath; | ||
| } | ||
|
|
||
| if (string.IsNullOrWhiteSpace(request.Models)) | ||
| { | ||
| return null; | ||
| } | ||
|
|
||
| try | ||
| { | ||
| var modelsArray = JsonSerializer.Deserialize<JsonElement[]>(request.Models); | ||
| if (modelsArray is { Length: > 0 }) | ||
| { | ||
| var firstModel = modelsArray[0]; | ||
| if (firstModel.TryGetProperty("path", out var pathElement)) | ||
| { | ||
| return pathElement.GetString(); | ||
| } | ||
| } | ||
| } | ||
| catch | ||
| { | ||
| // Swallow JSON parsing errors and fall back to form values | ||
| } | ||
|
|
||
| return null; | ||
| } | ||
| } | ||
| } |
74 changes: 74 additions & 0 deletions
74
KendoUI.FileManager.BlobStorage/KendoUI.FileManager.BlobStorage/Helpers/BlobPathHelper.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,74 @@ | ||
| using System.IO; | ||
|
|
||
| namespace KendoUI.FileManager.BlobStorage.Helpers | ||
| { | ||
| internal static class BlobPathHelper | ||
| { | ||
| public static string BuildPrefix(string? target) | ||
| { | ||
| return string.IsNullOrWhiteSpace(target) | ||
| ? string.Empty | ||
| : target.TrimEnd('/') + "/"; | ||
| } | ||
|
|
||
| public static string CombinePath(string? target, string name) | ||
| { | ||
| var prefix = BuildPrefix(target); | ||
| return string.IsNullOrEmpty(prefix) ? name : prefix + name; | ||
| } | ||
|
|
||
| public static string EnsureTrailingSlash(string? path) | ||
| { | ||
| if (string.IsNullOrEmpty(path)) | ||
| { | ||
| return path ?? string.Empty; | ||
| } | ||
|
|
||
| return path.EndsWith('/') ? path : path + "/"; | ||
| } | ||
|
|
||
| public static bool ShouldTreatAsDirectory(string name, string? isDirectoryFlag, int entry) | ||
| { | ||
| if (!string.IsNullOrEmpty(isDirectoryFlag) && bool.TryParse(isDirectoryFlag, out var isDirectory) && isDirectory) | ||
| { | ||
| return true; | ||
| } | ||
|
|
||
| if (entry == 1) | ||
| { | ||
| return true; | ||
| } | ||
|
|
||
| return !HasExtension(name); | ||
| } | ||
|
|
||
| public static string BuildNewPath(string targetPath, string newName, bool isDirectory) | ||
| { | ||
| var lastSlashIndex = targetPath.LastIndexOf('/'); | ||
|
|
||
| if (isDirectory) | ||
| { | ||
| return lastSlashIndex >= 0 | ||
| ? targetPath.Substring(0, lastSlashIndex + 1) + newName | ||
| : newName; | ||
| } | ||
|
|
||
| var originalExtension = Path.GetExtension(targetPath); | ||
| var newExtension = Path.GetExtension(newName); | ||
|
|
||
| if (string.IsNullOrEmpty(newExtension) && !string.IsNullOrEmpty(originalExtension)) | ||
| { | ||
| newName += originalExtension; | ||
| } | ||
|
|
||
| return lastSlashIndex >= 0 | ||
| ? targetPath.Substring(0, lastSlashIndex + 1) + newName | ||
| : newName; | ||
| } | ||
|
|
||
| public static bool HasExtension(string? name) | ||
| { | ||
| return !string.IsNullOrEmpty(name) && !string.IsNullOrEmpty(Path.GetExtension(name)); | ||
| } | ||
| } | ||
| } |
32 changes: 32 additions & 0 deletions
32
...anager.BlobStorage/KendoUI.FileManager.BlobStorage/KendoUI.FileManager.BlobStorage.csproj
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,32 @@ | ||
| <Project Sdk="Microsoft.NET.Sdk.Web"> | ||
|
|
||
| <PropertyGroup> | ||
| <TargetFramework>net10.0</TargetFramework> | ||
| <Nullable>enable</Nullable> | ||
| <ImplicitUsings>enable</ImplicitUsings> | ||
| </PropertyGroup> | ||
|
|
||
| <ItemGroup> | ||
| <PackageReference Include="Azure.Storage.Blobs" Version="12.26.0" /> | ||
| <PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="10.0.0" /> | ||
| <PackageReference Include="Telerik.UI.for.AspNet.Core" Version="2025.4.1111" /> | ||
| </ItemGroup> | ||
|
|
||
| <PropertyGroup Condition=" '$(Configuration)' == 'Release' "> | ||
| <DefineConstants>$(DefineConstants);RELEASE</DefineConstants> | ||
| </PropertyGroup> | ||
|
|
||
| <ItemGroup> | ||
| <Compile Remove="Templates\**" /> | ||
| <Content Remove="Templates\**" /> | ||
| <EmbeddedResource Remove="Templates\**" /> | ||
| <None Remove="Templates\**" /> | ||
| </ItemGroup> | ||
|
|
||
| <ProjectExtensions> | ||
| <VisualStudio> | ||
| <UserProperties UseCdnSupport="True" /> | ||
| </VisualStudio> | ||
| </ProjectExtensions> | ||
|
|
||
| </Project> |
31 changes: 31 additions & 0 deletions
31
...ileManager.BlobStorage/KendoUI.FileManager.BlobStorage/Models/FileManagerCreateContext.cs
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,31 @@ | ||
| using Microsoft.AspNetCore.Http; | ||
| using System.Linq; | ||
|
|
||
| namespace KendoUI.FileManager.BlobStorage.Models | ||
| { | ||
| public sealed class FileManagerCreateContext | ||
| { | ||
| public IFormFile? UploadedFile { get; init; } | ||
| public string? SourcePath { get; init; } | ||
| public string? Extension { get; init; } | ||
| public string? IsDirectoryFlag { get; init; } | ||
|
|
||
| public static FileManagerCreateContext FromRequest(FileManagerCreateRequest request) | ||
| { | ||
| if (request is null) | ||
| { | ||
| throw new ArgumentNullException(nameof(request)); | ||
| } | ||
|
|
||
| return new FileManagerCreateContext | ||
| { | ||
| UploadedFile = request.Files?.FirstOrDefault(), | ||
| SourcePath = request.Path ?? | ||
| request.Source ?? | ||
| request.SourcePath, | ||
| Extension = request.Extension, | ||
| IsDirectoryFlag = request.IsDirectoryFlag | ||
| }; | ||
| } | ||
| } | ||
| } | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we double-check if this class is really needed or if we can simplify?