diff --git a/RuleManagerWeb/.vscode/launch.json b/RuleManagerWeb/.vscode/launch.json new file mode 100644 index 0000000..71e037e --- /dev/null +++ b/RuleManagerWeb/.vscode/launch.json @@ -0,0 +1,35 @@ +{ + "version": "0.2.0", + "configurations": [ + { + // Use IntelliSense to find out which attributes exist for C# debugging + // Use hover for the description of the existing attributes + // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md + "name": ".NET Core Launch (web)", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "build", + // If you have changed target frameworks, make sure to update the program path. + "program": "${workspaceFolder}/RuleManager.UI/bin/Debug/net6.0/RuleManager.UI.dll", + "args": [], + "cwd": "${workspaceFolder}/RuleManager.UI", + "stopAtEntry": false, + // Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser + "serverReadyAction": { + "action": "openExternally", + "pattern": "\\bNow listening on:\\s+(https?://\\S+)" + }, + "env": { + "ASPNETCORE_ENVIRONMENT": "Development" + }, + "sourceFileMap": { + "/Views": "${workspaceFolder}/Views" + } + }, + { + "name": ".NET Core Attach", + "type": "coreclr", + "request": "attach" + } + ] +} \ No newline at end of file diff --git a/RuleManagerWeb/.vscode/tasks.json b/RuleManagerWeb/.vscode/tasks.json new file mode 100644 index 0000000..bb0badc --- /dev/null +++ b/RuleManagerWeb/.vscode/tasks.json @@ -0,0 +1,42 @@ +{ + "version": "2.0.0", + "tasks": [ + { + "label": "build", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/RuleManager.UI/RuleManager.UI.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "publish", + "command": "dotnet", + "type": "process", + "args": [ + "publish", + "${workspaceFolder}/RuleManager.UI/RuleManager.UI.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + }, + { + "label": "watch", + "command": "dotnet", + "type": "process", + "args": [ + "watch", + "run", + "${workspaceFolder}/RuleManager.UI/RuleManager.UI.csproj", + "/property:GenerateFullPaths=true", + "/consoleloggerparameters:NoSummary" + ], + "problemMatcher": "$msCompile" + } + ] +} \ No newline at end of file diff --git a/RuleManagerWeb/RuleManager.UI.sln b/RuleManagerWeb/RuleManager.UI.sln index 6a82483..eaa1eb6 100644 --- a/RuleManagerWeb/RuleManager.UI.sln +++ b/RuleManagerWeb/RuleManager.UI.sln @@ -5,7 +5,7 @@ VisualStudioVersion = 17.0.31912.275 MinimumVisualStudioVersion = 10.0.40219.1 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RuleManager.UI", "RuleManager.UI\RuleManager.UI.csproj", "{9748A89B-FB74-42D3-81FD-8AD724F8CC16}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "RuleManager.Data", "RuleManager.Data\RuleManager.Data.csproj", "{E707EACA-EE1A-49F5-BD5E-1766C9B9224C}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "RuleManager.Data", "RuleManager.Data\RuleManager.Data.csproj", "{E707EACA-EE1A-49F5-BD5E-1766C9B9224C}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution diff --git a/RuleManagerWeb/RuleManager.UI/Data/FileSaveController.cs b/RuleManagerWeb/RuleManager.UI/Data/FileSaveController.cs new file mode 100644 index 0000000..598389c --- /dev/null +++ b/RuleManagerWeb/RuleManager.UI/Data/FileSaveController.cs @@ -0,0 +1,99 @@ +using System; +using System.Collections.Generic; +using System.IO; +using System.Net; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Hosting; +using Microsoft.AspNetCore.Http; +using Microsoft.AspNetCore.Mvc; +using Microsoft.Extensions.Logging; + +[ApiController] +[Route("[controller]")] +public class FilesaveController : ControllerBase +{ + private readonly IWebHostEnvironment env; + private readonly ILogger logger; + + public FilesaveController(IWebHostEnvironment env, + ILogger logger) + { + this.env = env; + this.logger = logger; + } + + [HttpPost] + public async Task>> PostFile( + [FromForm] IEnumerable files) + { + var maxAllowedFiles = 3; + long maxFileSize = 1024 * 1024 * 15; + var filesProcessed = 0; + var resourcePath = new Uri($"{Request.Scheme}://{Request.Host}/"); + List uploadResults = new(); + + foreach (var file in files) + { + var uploadResult = new UploadResult(); + string trustedFileNameForFileStorage; + var untrustedFileName = file.FileName; + uploadResult.FileName = untrustedFileName; + var trustedFileNameForDisplay = + WebUtility.HtmlEncode(untrustedFileName); + + if (filesProcessed < maxAllowedFiles) + { + if (file.Length == 0) + { + logger.LogInformation("{FileName} length is 0 (Err: 1)", + trustedFileNameForDisplay); + uploadResult.ErrorCode = 1; + } + else if (file.Length > maxFileSize) + { + logger.LogInformation("{FileName} of {Length} bytes is " + + "larger than the limit of {Limit} bytes (Err: 2)", + trustedFileNameForDisplay, file.Length, maxFileSize); + uploadResult.ErrorCode = 2; + } + else + { + try + { + trustedFileNameForFileStorage = Path.GetRandomFileName(); + var path = Path.Combine(env.ContentRootPath, + env.EnvironmentName, "unsafe_uploads", + trustedFileNameForFileStorage); + + await using FileStream fs = new(path, FileMode.Create); + await file.CopyToAsync(fs); + + logger.LogInformation("{FileName} saved at {Path}", + trustedFileNameForDisplay, path); + uploadResult.Uploaded = true; + uploadResult.StoredFileName = trustedFileNameForFileStorage; + } + catch (IOException ex) + { + logger.LogError("{FileName} error on upload (Err: 3): {Message}", + trustedFileNameForDisplay, ex.Message); + uploadResult.ErrorCode = 3; + } + } + + filesProcessed++; + } + else + { + logger.LogInformation("{FileName} not uploaded because the " + + "request exceeded the allowed {Count} of files (Err: 4)", + trustedFileNameForDisplay, maxAllowedFiles); + uploadResult.ErrorCode = 4; + } + + uploadResults.Add(uploadResult); + } + + return new CreatedResult(resourcePath, uploadResults); + } +} \ No newline at end of file diff --git a/RuleManagerWeb/RuleManager.UI/Data/UploadResult.cs b/RuleManagerWeb/RuleManager.UI/Data/UploadResult.cs new file mode 100644 index 0000000..4f9cc13 --- /dev/null +++ b/RuleManagerWeb/RuleManager.UI/Data/UploadResult.cs @@ -0,0 +1,7 @@ +public class UploadResult +{ + public bool Uploaded { get; set; } + public string FileName { get; set; } + public string StoredFileName { get; set; } + public int ErrorCode { get; set; } +} \ No newline at end of file diff --git a/RuleManagerWeb/RuleManager.UI/Pages/FileUpload2.razor b/RuleManagerWeb/RuleManager.UI/Pages/FileUpload2.razor new file mode 100644 index 0000000..d4b4eaf --- /dev/null +++ b/RuleManagerWeb/RuleManager.UI/Pages/FileUpload2.razor @@ -0,0 +1,47 @@ +@page "/fileupload2" +@using System.Linq +@using System.Net.Http.Headers +@using System.Text.Json +@using Microsoft.Extensions.Logging +@inject IHttpClientFactory ClientFactory + +

Upload Files

+ +

+ +

+ +@if (files.Count > 0) +{ +
+
+
    + @foreach (var file in files) + { +
  • + File: @file.Name +
    + @if (FileUpload(uploadResults, file.Name, Logger, + out var result)) + { + + Stored File Name: @result.StoredFileName + + } + else + { + + There was an error uploading the file + (Error: @result.ErrorCode). + + } +
  • + } +
+
+
+} + diff --git a/RuleManagerWeb/RuleManager.UI/Pages/FileUpload2.razor.cs b/RuleManagerWeb/RuleManager.UI/Pages/FileUpload2.razor.cs new file mode 100644 index 0000000..e6cf1c8 --- /dev/null +++ b/RuleManagerWeb/RuleManager.UI/Pages/FileUpload2.razor.cs @@ -0,0 +1,98 @@ +using System; +using System.Collections.Generic; +using System.Threading.Tasks; +using Microsoft.AspNetCore.Components; +using System.Net.Http; +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Components.Authorization; +using Microsoft.AspNetCore.Components.Forms; +using Microsoft.AspNetCore.Components.Routing; +using Microsoft.AspNetCore.Components.Web; +using Microsoft.AspNetCore.Components.Web.Virtualization; +using Microsoft.JSInterop; +using RuleManager.UI; +using RuleManager.UI.Shared; +using System.Linq; +using System.Net.Http.Headers; +using System.Text.Json; +using Microsoft.Extensions.Logging; + +namespace RuleManager.UI.Pages +{ + public partial class FileUpload2 + { + + + [Inject] + private ILogger? Logger { get; set; } + + private List files = new(); + private List uploadResults = new(); + private int maxAllowedFiles = 3; + private bool shouldRender; + protected override bool ShouldRender() => shouldRender; + private async Task OnInputFileChange(InputFileChangeEventArgs e) + { + shouldRender = false; + long maxFileSize = 1024 * 1024 * 15; + var upload = false; + using var content = new MultipartFormDataContent(); + foreach (var file in e.GetMultipleFiles(maxAllowedFiles)) + { + if (uploadResults.SingleOrDefault(f => f.FileName == file.Name)is null) + { + try + { + var fileContent = new StreamContent(file.OpenReadStream(maxFileSize)); + fileContent.Headers.ContentType = new MediaTypeHeaderValue(file.ContentType); + files.Add(new() + {Name = file.Name}); + content.Add(content: fileContent, name: "\"files\"", fileName: file.Name); + upload = true; + } + catch (Exception ex) + { + Logger.LogInformation("{FileName} not uploaded (Err: 6): {Message}", file.Name, ex.Message); + uploadResults.Add(new() + {FileName = file.Name, ErrorCode = 6, Uploaded = false}); + } + } + } + + if (upload) + { + var client = ClientFactory.CreateClient(); + var response = await client.PostAsync("https://localhost:5001/Filesave", content); + if (response.IsSuccessStatusCode) + { + var options = new JsonSerializerOptions{PropertyNameCaseInsensitive = true, }; + using var responseStream = await response.Content.ReadAsStreamAsync(); + var newUploadResults = await JsonSerializer.DeserializeAsync>(responseStream, options); + if (newUploadResults is not null) + { + uploadResults = uploadResults.Concat(newUploadResults).ToList(); + } + } + } + + shouldRender = true; + } + + private static bool FileUpload(IList uploadResults, string? fileName, ILogger logger, out UploadResult result) + { + result = uploadResults.SingleOrDefault(f => f.FileName == fileName) ?? new(); + if (!result.Uploaded) + { + logger.LogInformation("{FileName} not uploaded (Err: 5)", fileName); + result.ErrorCode = 5; + } + + return result.Uploaded; + } + + private class File + { + public string? Name { get; set; } + } + } +} \ No newline at end of file diff --git a/RuleManagerWeb/RuleManager.UI/Pages/TestUpload.cshtml b/RuleManagerWeb/RuleManager.UI/Pages/TestUpload.cshtml new file mode 100644 index 0000000..f7500ec --- /dev/null +++ b/RuleManagerWeb/RuleManager.UI/Pages/TestUpload.cshtml @@ -0,0 +1,69 @@ +@page "/Testupload" + +Test upload + +

File Upload

+ +@*
+
+
+ +
+
+ + +
+
+ +
*@ + +
+
+
+ +
+
+ +
+
+ + + +
+ +
+
+ + + +@*

Current count: @currentCount

*@ + +@**@ + +@model RuleManager.UI.Pages.IndexModel +@{ +} diff --git a/RuleManagerWeb/RuleManager.UI/Pages/TestUpload.cshtml.cs b/RuleManagerWeb/RuleManager.UI/Pages/TestUpload.cshtml.cs new file mode 100644 index 0000000..42a7aec --- /dev/null +++ b/RuleManagerWeb/RuleManager.UI/Pages/TestUpload.cshtml.cs @@ -0,0 +1,13 @@ +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.RazorPages; + +namespace RuleManager.UI.Pages +{ + public class IndexModel : PageModel + { + public void OnGet() + { + + } + } +} diff --git a/RuleManagerWeb/RuleManager.UI/Pages/Upload.razor b/RuleManagerWeb/RuleManager.UI/Pages/Upload.razor new file mode 100644 index 0000000..d814e67 --- /dev/null +++ b/RuleManagerWeb/RuleManager.UI/Pages/Upload.razor @@ -0,0 +1,119 @@ +@page "/upload" +@using System +@using System.IO +@using System.Globalization +@using Microsoft.AspNetCore.Hosting +@using Microsoft.Extensions.Logging +@using System.Linq +@using CsvHelper +@using CsvHelper.Configuration +@using CsvHelper.Configuration.Attributes +@*@inject ILogger Logger*@ +@inject ILogger Logger +@inject IWebHostEnvironment Environment + +

Upload Files

+ +

+ +

+ +

+ +

+ +

+ +

+ +@if (isLoading) +{ +

Uploading...

+} +else +{ +
    + @foreach (var file in loadedFiles) + { +
  • +
      +
    • Name: @file.Name
    • +
    • Last modified: @file.LastModified.ToString()
    • +
    • Size (bytes): @file.Size
    • +
    • Content type: @file.ContentType
    • +
    +
  • + } +
+} + +@code { + private List loadedFiles = new(); + private long maxFileSize = 1024 * 15; + private int maxAllowedFiles = 3; + private bool isLoading; + + private async Task LoadFiles(InputFileChangeEventArgs e) + { + isLoading = true; + loadedFiles.Clear(); + + foreach (var file in e.GetMultipleFiles(maxAllowedFiles)) + { + try + { + loadedFiles.Add(file); + + var trustedFileNameForFileStorage = Path.GetRandomFileName(); + var path = Path.Combine(Environment.ContentRootPath, + Environment.EnvironmentName, "unsafe_uploads", + trustedFileNameForFileStorage); + + await using FileStream fs = new(path, FileMode.Create); + await file.OpenReadStream(maxFileSize).CopyToAsync(fs); + + //using (var csvReader = new CsvReader(fs, CultureInfo.InvariantCulture)); + //{ + // var records = CsvReader.GetRecords().ToList(); + + //} + + } + catch (Exception ex) + { + Logger.LogError("File: {Filename} Error: {Error}", + file.Name, ex.Message); + } + + } + + isLoading = false; + } + + public class RocketLaunch + { + [Name("flight_number") ] + public int FlightNumber { get; set; } + + [Name("name") ] + public string MissionName { get; set; } + + [Name("launch_date") ] + public DateTime LaunchDate { get; set; } + + [Name("success") ] + public bool Succeded { get; set; } + + [Name("booster_recovered") ] + public bool DidLand { get; set; } + } +} \ No newline at end of file diff --git a/RuleManagerWeb/RuleManager.UI/RuleManager.UI.csproj b/RuleManagerWeb/RuleManager.UI/RuleManager.UI.csproj index 2764225..30f64bf 100644 --- a/RuleManagerWeb/RuleManager.UI/RuleManager.UI.csproj +++ b/RuleManagerWeb/RuleManager.UI/RuleManager.UI.csproj @@ -1,4 +1,4 @@ - + net6.0 @@ -7,6 +7,7 @@ + diff --git a/RuleManagerWeb/RuleManager.UI/Shared/NavMenu.razor b/RuleManagerWeb/RuleManager.UI/Shared/NavMenu.razor index faedaa7..0038e62 100644 --- a/RuleManagerWeb/RuleManager.UI/Shared/NavMenu.razor +++ b/RuleManagerWeb/RuleManager.UI/Shared/NavMenu.razor @@ -24,6 +24,21 @@ Fetch data + + +