From 10f7a23d31f796ba6d63235db33bcda578b1e604 Mon Sep 17 00:00:00 2001 From: James Kolpack Date: Wed, 3 Feb 2016 13:39:50 -0500 Subject: [PATCH] Leaf Input functional --- Web/Attributes/HttpParamActionAttribute.cs | 31 +++++++ Web/Controllers/ControllerBase.cs | 27 ++++++ Web/Controllers/LeafInputController.cs | 93 ++++++++++++++++---- Web/Scripts/LeafInput.js | 5 +- Web/ViewModels/LeafInput/ConfirmViewModel.cs | 32 +++++++ Web/ViewModels/LeafInput/CreateViewModel.cs | 22 ++++- Web/Views/LeafInput/Confirm.cshtml | 53 +++++++++++ Web/Views/LeafInput/Index.cshtml | 26 +++--- Web/Views/Shared/_StatusMessage.cshtml | 14 +++ Web/Web.csproj | 38 +++++--- Web/packages.config | 3 +- 11 files changed, 299 insertions(+), 45 deletions(-) create mode 100644 Web/Attributes/HttpParamActionAttribute.cs create mode 100644 Web/ViewModels/LeafInput/ConfirmViewModel.cs create mode 100644 Web/Views/LeafInput/Confirm.cshtml create mode 100644 Web/Views/Shared/_StatusMessage.cshtml diff --git a/Web/Attributes/HttpParamActionAttribute.cs b/Web/Attributes/HttpParamActionAttribute.cs new file mode 100644 index 0000000..90c548c --- /dev/null +++ b/Web/Attributes/HttpParamActionAttribute.cs @@ -0,0 +1,31 @@ +using System; +using System.Diagnostics.Contracts; +using System.Reflection; +using System.Web.Mvc; + +namespace LeafWeb.Web.Attributes +{ + /// + /// Add to actions to use multiple submit buttons (back or save, for example) + /// + /// + /// http://blog.ashmind.com/2010/03/15/multiple-submit-buttons-with-asp-net-mvc-final-solution/ + /// https://github.com/ashmind/lightwiki/blob/master/$libraries/AshMind.Web.Mvc/HttpParamActionAttribute.cs + /// + public class HttpParamActionAttribute : ActionNameSelectorAttribute + { + [Pure] + [ContractVerification(false)] + public override bool IsValidName(ControllerContext controllerContext, string actionName, MethodInfo methodInfo) + { + if (actionName.Equals(methodInfo.Name, StringComparison.InvariantCultureIgnoreCase)) + return true; + + if (!actionName.Equals("Action", StringComparison.InvariantCultureIgnoreCase)) + return false; + + var request = controllerContext.RequestContext.HttpContext.Request; + return request[methodInfo.Name] != null; + } + } +} \ No newline at end of file diff --git a/Web/Controllers/ControllerBase.cs b/Web/Controllers/ControllerBase.cs index c460e3e..70d19c4 100644 --- a/Web/Controllers/ControllerBase.cs +++ b/Web/Controllers/ControllerBase.cs @@ -17,5 +17,32 @@ namespace LeafWeb.Web.Controllers DataService.Dispose(); base.Dispose(disposing); } + + protected bool IsHttpParamActionMatch() + { + return ControllerContext.RouteData.Values["action"].ToString() + .Equals("Action", StringComparison.InvariantCultureIgnoreCase); + } + + protected enum StatusType + { + Info, + Success, + Error + } + + protected void SetStatusMessage(string msg, StatusType statusType = StatusType.Info) + { + TempData["StatusMessage"] = msg; + switch (statusType) + { + case StatusType.Success: + TempData["StatusMessage-Type"] = "alert-success"; + break; + case StatusType.Error: + TempData["StatusMessage-Type"] = "alert-error"; + break; + } + } } } \ No newline at end of file diff --git a/Web/Controllers/LeafInputController.cs b/Web/Controllers/LeafInputController.cs index ea2d005..1dc82ce 100644 --- a/Web/Controllers/LeafInputController.cs +++ b/Web/Controllers/LeafInputController.cs @@ -1,27 +1,71 @@ using System.IO; using System.Linq; +using System.Web; using System.Web.Mvc; -using LeafWeb.Core.DAL; +using LeafWeb.Core.Models; +using LeafWeb.Web.Attributes; using LeafWeb.Web.ViewModels.LeafInput; namespace LeafWeb.Web.Controllers { - public class LeafInputController : Controller - { - public ActionResult Index() - { - // initialize the session storage to retain SessionID between requests + public class LeafInputController : ControllerBase + { + public ActionResult Index() + { + // initialize the session storage to retain SessionID between requests Session["placeholder"] = 0; - return View(); - } + return View(); + } + private FileInfo[] GetBackloadDirectoryFiles(string directoryName) + { + var path = Path.Combine(Server.MapPath("~/Files/"), directoryName + "\\"); + var directory = new DirectoryInfo(path); + if (!directory.Exists) + { + return new FileInfo[] {}; + } + + return directory.GetFiles(); + } + + private void DeleteBackloadDirectory(string directoryName) + { + var path = Path.Combine(Server.MapPath("~/Files/"), directoryName + "\\"); + var directory = new DirectoryInfo(path); + if (directory.Exists) + { + directory.Delete(true); + } + } + + [HttpParamAction] [HttpPost] - public ActionResult Index(CreateViewModel viewModel) - { + public ActionResult Index(CreateViewModel viewModel) + { // directory name is the sessionID - var directory = Session.SessionID; - var path = Path.Combine(Server.MapPath("~/Files/"), directory + "\\"); - var files = Directory.GetFiles(path); + var files = GetBackloadDirectoryFiles(Session.SessionID); + + if (!files.Any()) + { + ModelState.AddModelError("", "Must select at least one file"); + } + + if (ModelState.IsValid && !IsHttpParamActionMatch()) + { + // Go to confirmation + var confirmViewModel = new ConfirmViewModel(viewModel, files.Select(f => f.Name).ToArray()); + return View("Confirm", confirmViewModel); + } + return View("Index"); + } + + [HttpParamAction] + [HttpPost] + public ActionResult Confirm(CreateViewModel viewModel) + { + // directory name is the sessionID + var files = GetBackloadDirectoryFiles(Session.SessionID); if (!files.Any()) { @@ -31,12 +75,25 @@ namespace LeafWeb.Web.Controllers if (ModelState.IsValid) { // convert viewModel into Model - + var model = viewModel.GetFileInput(); // load files into LeafInputFile + var leafInputFiles = + from f in files + let bytes = System.IO.File.ReadAllBytes(f.FullName) + select new LeafInputFile {Filename = f.Name, Contents = bytes}; - // + // TODO: Save to db + + DeleteBackloadDirectory(Session.SessionID); + + SetStatusMessage( + HttpUtility.HtmlEncode( + $"A data set has submitted for '{viewModel.Identifier}' from '{viewModel.SiteId}'. " + + $"When complete, an email will be delivered to {viewModel.Name} <{viewModel.Email}> with results."), + StatusType.Success); + return RedirectToAction("Index"); } - return View(); - } - } + return View("Index", viewModel); + } + } } \ No newline at end of file diff --git a/Web/Scripts/LeafInput.js b/Web/Scripts/LeafInput.js index 244e87c..720001d 100644 --- a/Web/Scripts/LeafInput.js +++ b/Web/Scripts/LeafInput.js @@ -10,12 +10,13 @@ $("#fileupload").fileupload({ url: url, autoUpload: true, + maxFileSize: 5000000, maxChunkSize: 10000000, // Optional: file chunking with 10MB chunks - acceptFileTypes: /(bat)|(.*)$/i // Allowed file types + acceptFileTypes: /(csv)$/i // Allowed file types }) .bind("fileuploadsubmit", function (e, data) { // Optional: We add a random uuid form parameter. On chunk uploads the uuid is used to store the chunks. - data.formData = { uuid: Math.random().toString(36).substr(2, 8) }; + //data.formData = { uuid: Math.random().toString(36).substr(2, 8) }; }); // Load existing files: diff --git a/Web/ViewModels/LeafInput/ConfirmViewModel.cs b/Web/ViewModels/LeafInput/ConfirmViewModel.cs new file mode 100644 index 0000000..a028ec9 --- /dev/null +++ b/Web/ViewModels/LeafInput/ConfirmViewModel.cs @@ -0,0 +1,32 @@ +using System.ComponentModel.DataAnnotations; +using AutoMapper; + +namespace LeafWeb.Web.ViewModels.LeafInput +{ + public class ConfirmViewModel + { + private static readonly IMapper Mapper; + + public string Name { get; set; } + public string Email { get; set; } + public string Identifier { get; set; } + public string SiteId { get; set; } + public string[] Files { get; set; } + + static ConfirmViewModel() + { + var config = + new MapperConfiguration(cfg => + { + cfg.CreateMap(); + }); + Mapper = config.CreateMapper(); + } + + public ConfirmViewModel(CreateViewModel createViewModel, string[] files) + { + Mapper.Map(createViewModel, this); + Files = files; + } + } +} \ No newline at end of file diff --git a/Web/ViewModels/LeafInput/CreateViewModel.cs b/Web/ViewModels/LeafInput/CreateViewModel.cs index 3421a0e..9e0a6c5 100644 --- a/Web/ViewModels/LeafInput/CreateViewModel.cs +++ b/Web/ViewModels/LeafInput/CreateViewModel.cs @@ -1,10 +1,12 @@ using System.ComponentModel.DataAnnotations; -using System.Web; +using AutoMapper; namespace LeafWeb.Web.ViewModels.LeafInput { public class CreateViewModel { + private static readonly IMapper Mapper; + [Display(Name = "Your name")] [Required(ErrorMessage = "Name required")] [RegularExpression(@"[A-Za-z().]+(\s+[A-Za-z().]+)+", ErrorMessage = "Please provide your full name")] @@ -28,5 +30,23 @@ namespace LeafWeb.Web.ViewModels.LeafInput [Display(Name = "The site's name/Fluxnet ID, if known")] [Required(ErrorMessage = "The site's name is required")] public string SiteId { get; set; } + + + static CreateViewModel() + { + var config = + new MapperConfiguration(cfg => + { + cfg.CreateMap(); + }); + Mapper = config.CreateMapper(); + } + + public Core.Models.LeafInput GetFileInput() + { + var leafInput = new Core.Models.LeafInput(); + Mapper.Map(this, leafInput); + return leafInput; + } } } \ No newline at end of file diff --git a/Web/Views/LeafInput/Confirm.cshtml b/Web/Views/LeafInput/Confirm.cshtml new file mode 100644 index 0000000..b685d7b --- /dev/null +++ b/Web/Views/LeafInput/Confirm.cshtml @@ -0,0 +1,53 @@ +@model LeafWeb.Web.ViewModels.LeafInput.ConfirmViewModel + +

Please confirm - entry not submitted until confirm clicked

+ +
+
+ @using (Html.BeginForm("Action", "LeafInput", FormMethod.Post)) + { +
+
+ @Html.DisplayNameFor(m => m.Name) +
+
+ @Html.DisplayTextFor(m => m.Name) +
+
+ @Html.HiddenFor(m => m.Name) + + @Html.HiddenFor(m => m.Email) + +
+
+ @Html.DisplayNameFor(m => m.SiteId) +
+
+ @Html.DisplayTextFor(m => m.SiteId) +
+
+ @Html.HiddenFor(m => m.SiteId) +
+
+ @Html.DisplayNameFor(m => m.Identifier) +
+
+ @Html.DisplayTextFor(m => m.Identifier) +
+
+ @Html.HiddenFor(m => m.Identifier) + +
+ + +
+ } +
+
diff --git a/Web/Views/LeafInput/Index.cshtml b/Web/Views/LeafInput/Index.cshtml index 7036d23..56e2646 100644 --- a/Web/Views/LeafInput/Index.cshtml +++ b/Web/Views/LeafInput/Index.cshtml @@ -1,25 +1,27 @@ @model LeafWeb.Web.ViewModels.LeafInput.CreateViewModel - -

Submitting Data and Retrieving EDO Results

-

- There is no limit on the number of files you may submit for analysis. Keep selecting files and hitting the Add button until all of the files you need to upload are shown in the list. Then enter an identifier for this set of data and click the Upload button. -

@section Styles { @Styles.Render("~/backload/blueimp/bootstrap/BasicPlusUI/css") } +

Submitting Data and Retrieving EDO Results

+

+ There is no limit on the number of files you may submit for analysis. Keep selecting files and hitting the Add button until all of the files you need to upload are shown in the list. Then enter an identifier for this set of data and click the Upload button. +

+ +@Html.Partial("_StatusMessage") +
- @using (Html.BeginForm("Index", "LeafInput", FormMethod.Post, new { enctype = "multipart/form-data" })) + @using (Html.BeginForm("Index", "LeafInput", FormMethod.Post)) { @Html.Partial("_ValidationSummary") - @Html.EditorFor(m => m.Name) - @Html.EditorFor(m => m.Email) - @Html.EditorFor(m => m.EmailConfirm) - @Html.EditorFor(m => m.Identifier) - @Html.EditorFor(m => m.SiteId) - + @Html.EditorFor(m => m.Name) + @Html.EditorFor(m => m.Email) + @Html.EditorFor(m => m.EmailConfirm) + @Html.EditorFor(m => m.Identifier) + @Html.EditorFor(m => m.SiteId) + }
diff --git a/Web/Views/Shared/_StatusMessage.cshtml b/Web/Views/Shared/_StatusMessage.cshtml new file mode 100644 index 0000000..ab19134 --- /dev/null +++ b/Web/Views/Shared/_StatusMessage.cshtml @@ -0,0 +1,14 @@ +@if (TempData.ContainsKey("StatusMessage")) +{ +

+ + @Html.Raw(TempData["StatusMessage"]) +

+} +else if (ViewBag.StatusMessage != null) +{ +

+ + @ViewBag.StatusMessage +

+} diff --git a/Web/Web.csproj b/Web/Web.csproj index ceac542..0caa75d 100644 --- a/Web/Web.csproj +++ b/Web/Web.csproj @@ -20,6 +20,8 @@ + + true @@ -43,6 +45,10 @@ ..\packages\Antlr.3.4.1.9004\lib\Antlr3.Runtime.dll True + + ..\packages\AutoMapper.4.2.0\lib\net45\AutoMapper.dll + True + ..\packages\Backload.Core.2.1.5.4\lib\net45\Backload.dll True @@ -65,11 +71,15 @@ - ..\packages\Microsoft.Bcl.Async.1.0.16\lib\net45\Microsoft.Threading.Tasks.dll + ..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.dll True - ..\packages\Microsoft.Bcl.Async.1.0.16\lib\net45\Microsoft.Threading.Tasks.Extensions.dll + ..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.dll + True + + + ..\packages\Microsoft.Bcl.Async.1.0.168\lib\net40\Microsoft.Threading.Tasks.Extensions.Desktop.dll True @@ -403,6 +413,11 @@ + + + Designer + + @@ -478,11 +493,6 @@ - - - Designer - - @@ -889,9 +899,11 @@ Global.asax + + @@ -925,6 +937,8 @@ + + Web.config @@ -967,10 +981,12 @@ - - - - + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + +