From a3aaed1d13fa76e16ae267514ef0babf0cacf71e Mon Sep 17 00:00:00 2001 From: Gary Sharp Date: Mon, 22 Jul 2013 21:16:31 +1000 Subject: [PATCH] Feature: Device Importing - Initial --- Disco.BI/BI/DeviceBI/Importing/Import.cs | 227 ++++++++++++++++++ .../BI/DeviceBI/Importing/ImportParseTask.cs | 84 +++++++ .../DeviceBI/Importing/ImportProcessTask.cs | 67 ++++++ Disco.BI/Disco.BI.csproj | 6 + Disco.BI/Properties/AssemblyInfo.cs | 4 +- Disco.Client/Properties/AssemblyInfo.cs | 4 +- .../Properties/AssemblyInfo.cs | 4 +- Disco.Data/Properties/AssemblyInfo.cs | 4 +- Disco.Models/BI/Device/ImportDevice.cs | 40 +++ Disco.Models/Disco.Models.csproj | 2 + Disco.Models/Properties/AssemblyInfo.cs | 4 +- Disco.Models/UI/Device/DeviceImportModel.cs | 12 + Disco.Services/Plugins/PluginManifest.cs | 1 + Disco.Services/Properties/AssemblyInfo.cs | 4 +- .../Properties/AssemblyInfo.cs | 4 +- .../Areas/API/Controllers/DeviceController.cs | 22 ++ Disco.Web/Controllers/DeviceController.cs | 17 +- Disco.Web/Disco.Web.csproj | 12 +- Disco.Web/Models/Device/ImportModel.cs | 15 ++ Disco.Web/Properties/AssemblyInfo.cs | 4 +- Disco.Web/T4MVC.cs | 65 +++++ Disco.Web/Views/Device/Import.cshtml | 23 ++ Disco.Web/Views/Device/Import.generated.cs | 129 ++++++++++ Disco.Web/Views/Device/Index.cshtml | 1 + Disco.Web/Views/Device/Index.generated.cs | 13 +- 25 files changed, 748 insertions(+), 20 deletions(-) create mode 100644 Disco.BI/BI/DeviceBI/Importing/Import.cs create mode 100644 Disco.BI/BI/DeviceBI/Importing/ImportParseTask.cs create mode 100644 Disco.BI/BI/DeviceBI/Importing/ImportProcessTask.cs create mode 100644 Disco.Models/BI/Device/ImportDevice.cs create mode 100644 Disco.Models/UI/Device/DeviceImportModel.cs create mode 100644 Disco.Web/Models/Device/ImportModel.cs create mode 100644 Disco.Web/Views/Device/Import.cshtml create mode 100644 Disco.Web/Views/Device/Import.generated.cs diff --git a/Disco.BI/BI/DeviceBI/Importing/Import.cs b/Disco.BI/BI/DeviceBI/Importing/Import.cs new file mode 100644 index 00000000..b9efba24 --- /dev/null +++ b/Disco.BI/BI/DeviceBI/Importing/Import.cs @@ -0,0 +1,227 @@ +using Disco.BI.Extensions; +using Disco.Data.Repository; +using Disco.Models.BI.Device; +using Disco.Models.Repository; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using PopulateRecordReferences = System.Tuple, System.Collections.Generic.Dictionary, System.Collections.Generic.Dictionary>; + +namespace Disco.BI.DeviceBI.Importing +{ + internal static class Import + { + + internal static void ImportRecord(this ImportDevice device, DiscoDataContext dbContext, PopulateRecordReferences references) + { + // Skips If Errors + if (device.Errors == null || device.Errors.Count == 0) + { + // Re-Populate & Skip If Errors + device.PopulateRecord(dbContext, references); + if (device.Errors == null || device.Errors.Count == 0) + { + Device discoDevice = device.Device; + + if (discoDevice == null) + { + // New Device + discoDevice = new Device() + { + SerialNumber = device.SerialNumber.ToUpper(), + CreatedDate = DateTime.Now, + AllowUnauthenticatedEnrol = true, + }; + dbContext.Devices.Add(discoDevice); + } + + discoDevice.DeviceModelId = device.DeviceModelId; + discoDevice.DeviceProfileId = device.DeviceProfileId; + discoDevice.DeviceBatchId = device.DeviceBatchId; + discoDevice.Location = device.Location; + discoDevice.AssetNumber = device.AssetNumber; + + if (discoDevice.AssignedUserId != device.AssignedUserId) + { + discoDevice.AssignDevice(dbContext, device.AssignedUser); + } + + dbContext.SaveChanges(); + } + } + } + + internal static PopulateRecordReferences GetPopulateRecordReferences(DiscoDataContext dbContext) + { + return new PopulateRecordReferences( + dbContext.DeviceModels.ToDictionary(dm => dm.Id), + dbContext.DeviceProfiles.ToDictionary(dp => dp.Id), + dbContext.DeviceBatches.ToDictionary(db => db.Id) + ); + } + + internal static void PopulateRecord(this ImportDevice device, DiscoDataContext dbContext, PopulateRecordReferences references) + { + + var deviceModels = references.Item1; + var deviceProfiles = references.Item2; + var deviceBatches = references.Item3; + + // SERIAL NUMBER - Existing Device + if (!device.Errors.ContainsKey("SerialNumber")) + device.Device = dbContext.Devices.Find(device.SerialNumber); + + // DEVICE MODEL + if (!device.Errors.ContainsKey("DeviceModelId")) + { + DeviceModel deviceModel; + + if (!device.DeviceModelId.HasValue) + device.DeviceModelId = 1; // Default 'Unknown Device Model' + + if (!deviceModels.TryGetValue(device.DeviceModelId.Value, out deviceModel)) + device.Errors.Add("DeviceModelId", string.Format("Unknown device model id: {0}", device.DeviceModelId)); + else + device.DeviceModel = deviceModel; + } + + // DEVICE PROFILE + if (!device.Errors.ContainsKey("DeviceProfileId")) + { + DeviceProfile deviceProfile; + if (!deviceProfiles.TryGetValue(device.DeviceProfileId, out deviceProfile)) + device.Errors.Add("DeviceProfileId", string.Format("Unknown device profile id: {0}", device.DeviceProfileId)); + else + device.DeviceProfile = deviceProfile; + } + + // DEVICE BATCH + if (!device.Errors.ContainsKey("DeviceBatchId") && device.DeviceBatchId.HasValue) + { + DeviceBatch deviceBatch; + if (!deviceBatches.TryGetValue(device.DeviceBatchId.Value, out deviceBatch)) + device.Errors.Add("DeviceBatchId", string.Format("Unknown device Batch id: {0}", device.DeviceBatchId)); + else + device.DeviceBatch = deviceBatch; + } + + // ASSIGNED USER + if (!device.Errors.ContainsKey("AssignedUserId") && device.AssignedUserId != null) + { + try + { + device.AssignedUser = UserBI.UserCache.GetUser(device.AssignedUserId, dbContext, true); + } + catch (ArgumentException) + { + device.Errors.Add("AssignedUserId", string.Format("Unknown user id: {0}", device.AssignedUserId)); + } + } + + } + + internal static ImportDevice ParseRecord(this string[] record) + { + int csvFieldCount = record.Length; + if (csvFieldCount < 1) + throw new ArgumentException("At least one CSV field is required (Serial Number)"); + + string csvSerialNumber; + string csvDeviceModelId; + int deviceModelId = 1; // Default 'Unknown Device Model' + string csvDeviceProfileId; + int deviceProfileId = 1; // 'Default' Profile + string csvDeviceBatchId; + int deviceBatchId = 0; // No Batch + string csvAssignedUserId = null; + string csvLocation = null; + string csvAssetNumber = null; + Dictionary errors = new Dictionary(); + + // SERIAL NUMBER + csvSerialNumber = record[0]; + if (string.IsNullOrWhiteSpace(csvSerialNumber)) + errors.Add("SerialNumber", "The serial number is required"); + else if (csvSerialNumber.Trim().Length > 60) + errors.Add("SerialNumber", "The serial number must be less than or equal to 60 characters"); + + if (csvFieldCount > 1) + { + // DEVICE MODEL + csvDeviceModelId = record[1]; + if (!string.IsNullOrWhiteSpace(csvDeviceModelId)) + if (!int.TryParse(csvDeviceModelId, out deviceModelId)) + errors.Add("DeviceModelId", "The device model is optional, but when supplied must be a number"); + else if (deviceModelId < 1) + errors.Add("DeviceModelId", "The device model is optional, but when supplied must be greater than 0"); + + if (csvFieldCount > 2) + { + // DEVICE PROFILE + csvDeviceProfileId = record[2]; + if (!string.IsNullOrWhiteSpace(csvDeviceProfileId)) + if (!int.TryParse(csvDeviceProfileId, out deviceProfileId)) + errors.Add("DeviceProfileId", "The device profile is optional, but when supplied must be a number"); + else if (deviceProfileId < 1) + errors.Add("DeviceProfileId", "The device profile is optional, but when supplied must be greater than 0"); + + if (csvFieldCount > 3) + { + // DEVICE BATCH + csvDeviceBatchId = record[3]; + if (!string.IsNullOrWhiteSpace(csvDeviceBatchId)) + if (!int.TryParse(csvDeviceBatchId, out deviceBatchId)) + errors.Add("DeviceBatchId", "The device batch is optional, but when supplied must be a number"); + else if (deviceBatchId < 1) + errors.Add("DeviceBatchId", "The device batch is optional, but when supplied must be greater than 0"); + + if (csvFieldCount > 4) + { + // ASSIGNED USER + csvAssignedUserId = record[4]; + if (string.IsNullOrWhiteSpace(csvAssignedUserId)) + csvAssignedUserId = null; // Not Assigned + else if (csvAssignedUserId.Length > 50) + errors.Add("AssignedUserId", "The assigned user must be less than or equal to 50 characters"); + + if (csvFieldCount > 5) + { + // LOCATION + csvLocation = record[5]; + if (string.IsNullOrWhiteSpace(csvLocation)) + csvLocation = null; // No Location Specified + else if (csvLocation.Length > 250) + errors.Add("Location", "The location must be less than or equal to 250 characters"); + + if (csvFieldCount > 6) + { + // ASSET NUMBER + csvAssetNumber = record[6]; + if (string.IsNullOrWhiteSpace(csvAssetNumber)) + csvAssetNumber = null; // No Location Specified + else if (csvAssetNumber.Length > 40) + errors.Add("AssetNumber", "The asset number must be less than or equal to 40 characters"); + } + } + } + } + } + } + + return new ImportDevice() + { + SerialNumber = csvSerialNumber.Trim(), + DeviceModelId = deviceModelId, + DeviceProfileId = deviceProfileId, + DeviceBatchId = deviceBatchId == 0 ? (int?)null : deviceBatchId, + AssignedUserId = csvAssignedUserId, + Location = csvLocation, + AssetNumber = csvAssetNumber, + Errors = errors + }; + } + } +} diff --git a/Disco.BI/BI/DeviceBI/Importing/ImportParseTask.cs b/Disco.BI/BI/DeviceBI/Importing/ImportParseTask.cs new file mode 100644 index 00000000..2a1eb431 --- /dev/null +++ b/Disco.BI/BI/DeviceBI/Importing/ImportParseTask.cs @@ -0,0 +1,84 @@ +using Disco.Data.Repository; +using Disco.Models.BI.Device; +using Disco.Models.Repository; +using Disco.Services.Tasks; +using LumenWorks.Framework.IO.Csv; +using Quartz; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Web; +using System.Web.Caching; + +namespace Disco.BI.DeviceBI.Importing +{ + public class ImportParseTask : ScheduledTask + { + public override string TaskName { get { return "Import Devices - Parsing"; } } + + public override bool SingleInstanceTask { get { return false; } } + public override bool CancelInitiallySupported { get { return false; } } + + internal const string ImportParseCacheKey = "ImportParseResults_{0}"; + + protected override void ExecuteTask() + { + MemoryStream csvStream = (MemoryStream)this.ExecutionContext.JobDetail.JobDataMap["CsvImport"]; + + this.Status.UpdateStatus(0, "Parsing CSV File", "Loading Records"); + + List records; + + using (TextReader csvTextReader = new StreamReader(csvStream)) + { + using (CsvReader csvReader = new CsvReader(csvTextReader, true)) + { + csvReader.DefaultParseErrorAction = ParseErrorAction.ThrowException; + csvReader.MissingFieldAction = MissingFieldAction.ReplaceByNull; + + records = csvReader.Select(record => record.ParseRecord()).ToList(); + } + } + csvStream.Dispose(); + + this.Status.UpdateStatus(20, "Parsing CSV File", string.Format("Linking {0} Records", records.Count)); + + using (DiscoDataContext dbContext = new DiscoDataContext()) + { + var populateReferences = Import.GetPopulateRecordReferences(dbContext); + + DateTime lastUpdate = DateTime.Now; + foreach (var record in records) + { + record.PopulateRecord(dbContext, populateReferences); + + if (DateTime.Now.Subtract(lastUpdate).TotalSeconds > 1) + { + // Update every second + this.Status.UpdateStatus((int)Math.Floor((((double)(records.IndexOf(record) + 1) / records.Count) * 80))); + lastUpdate = DateTime.Now; + } + } + } + + // Set Results to Cache + string key = string.Format(ImportParseCacheKey, this.Status.SessionId); + HttpRuntime.Cache.Insert(key, records, null, Cache.NoAbsoluteExpiration, TimeSpan.FromMinutes(60), CacheItemPriority.NotRemovable, null); + } + + public static ScheduledTaskStatus Run(Stream CsvImport) + { + + MemoryStream csvStream = new MemoryStream(); + CsvImport.CopyTo(csvStream); + csvStream.Position = 0; + + var task = new ImportParseTask(); + JobDataMap taskData = new JobDataMap() { { "CsvImport", csvStream } }; + return task.ScheduleTask(taskData); + } + } +} diff --git a/Disco.BI/BI/DeviceBI/Importing/ImportProcessTask.cs b/Disco.BI/BI/DeviceBI/Importing/ImportProcessTask.cs new file mode 100644 index 00000000..6841d9df --- /dev/null +++ b/Disco.BI/BI/DeviceBI/Importing/ImportProcessTask.cs @@ -0,0 +1,67 @@ +using Disco.Data.Repository; +using Disco.Models.BI.Device; +using Disco.Services.Tasks; +using Quartz; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using System.Web; + +namespace Disco.BI.DeviceBI.Importing +{ + public class ImportProcessTask : ScheduledTask + { + public override string TaskName { get { return "Import Devices - Processing Changes"; } } + + public override bool SingleInstanceTask { get { return false; } } + public override bool CancelInitiallySupported { get { return false; } } + + protected override void ExecuteTask() + { + string parseKey = (string)this.ExecutionContext.JobDetail.JobDataMap["ParseKey"]; + + if (string.IsNullOrWhiteSpace(parseKey)) + throw new ArgumentNullException("ParseKey"); + + parseKey = string.Format(ImportParseTask.ImportParseCacheKey, parseKey); + + List records = (List)HttpRuntime.Cache.Get(parseKey); + + if (records == null) + throw new InvalidOperationException("The session timed out (60 minutes), try importing again"); + + this.Status.UpdateStatus(0, "Processing Device Import", "Importing Devices"); + + using (DiscoDataContext dbContext = new DiscoDataContext()) + { + var populateReferences = Import.GetPopulateRecordReferences(dbContext); + + DateTime lastUpdate = DateTime.Now; + foreach (var record in records) + { + record.ImportRecord(dbContext, populateReferences); + + if (DateTime.Now.Subtract(lastUpdate).TotalSeconds > 1) + { + // Update every second + this.Status.UpdateStatus((int)Math.Floor((((double)(records.IndexOf(record) + 1) / records.Count) * 100)), string.Format("Importing: {0} ({1} of {2})", record.SerialNumber, records.IndexOf(record) + 1, records.Count)); + lastUpdate = DateTime.Now; + } + } + } + + } + + public static ScheduledTaskStatus Run(string ParseTaskSessionKey) + { + if (string.IsNullOrWhiteSpace(ParseTaskSessionKey)) + throw new ArgumentNullException("ParseTaskSessionKey"); + + var task = new ImportProcessTask(); + JobDataMap taskData = new JobDataMap() { { "ParseKey", ParseTaskSessionKey } }; + return task.ScheduleTask(taskData); + } + } +} diff --git a/Disco.BI/Disco.BI.csproj b/Disco.BI/Disco.BI.csproj index 91ef7289..3cd03c17 100644 --- a/Disco.BI/Disco.BI.csproj +++ b/Disco.BI/Disco.BI.csproj @@ -46,6 +46,9 @@ ..\Resources\Libraries\iTextSharp\itextsharp.dll + + ..\..\..\Resources\Libraries\LumenWorks.Framework.IO\LumenWorks.Framework.IO.dll + False ..\packages\Microsoft.AspNet.SignalR.Core.1.1.2\lib\net40\Microsoft.AspNet.SignalR.Core.dll @@ -120,6 +123,9 @@ + + + diff --git a/Disco.BI/Properties/AssemblyInfo.cs b/Disco.BI/Properties/AssemblyInfo.cs index 91f8c28f..8d5699b2 100644 --- a/Disco.BI/Properties/AssemblyInfo.cs +++ b/Disco.BI/Properties/AssemblyInfo.cs @@ -32,5 +32,5 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.2.0722.1715")] -[assembly: AssemblyFileVersion("1.2.0722.1715")] \ No newline at end of file +[assembly: AssemblyVersion("1.2.0722.2112")] +[assembly: AssemblyFileVersion("1.2.0722.2112")] \ No newline at end of file diff --git a/Disco.Client/Properties/AssemblyInfo.cs b/Disco.Client/Properties/AssemblyInfo.cs index 1659d342..87e5092d 100644 --- a/Disco.Client/Properties/AssemblyInfo.cs +++ b/Disco.Client/Properties/AssemblyInfo.cs @@ -32,5 +32,5 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.2.0722.1715")] -[assembly: AssemblyFileVersion("1.2.0722.1715")] \ No newline at end of file +[assembly: AssemblyVersion("1.2.0722.2115")] +[assembly: AssemblyFileVersion("1.2.0722.2115")] \ No newline at end of file diff --git a/Disco.ClientBootstrapper/Properties/AssemblyInfo.cs b/Disco.ClientBootstrapper/Properties/AssemblyInfo.cs index d3aedc4e..0cc521c2 100644 --- a/Disco.ClientBootstrapper/Properties/AssemblyInfo.cs +++ b/Disco.ClientBootstrapper/Properties/AssemblyInfo.cs @@ -32,5 +32,5 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.2.0722.1715")] -[assembly: AssemblyFileVersion("1.2.0722.1715")] \ No newline at end of file +[assembly: AssemblyVersion("1.2.0722.2047")] +[assembly: AssemblyFileVersion("1.2.0722.2047")] \ No newline at end of file diff --git a/Disco.Data/Properties/AssemblyInfo.cs b/Disco.Data/Properties/AssemblyInfo.cs index e1159a81..6cd47f19 100644 --- a/Disco.Data/Properties/AssemblyInfo.cs +++ b/Disco.Data/Properties/AssemblyInfo.cs @@ -32,5 +32,5 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.2.0722.1715")] -[assembly: AssemblyFileVersion("1.2.0722.1715")] \ No newline at end of file +[assembly: AssemblyVersion("1.2.0722.2112")] +[assembly: AssemblyFileVersion("1.2.0722.2112")] \ No newline at end of file diff --git a/Disco.Models/BI/Device/ImportDevice.cs b/Disco.Models/BI/Device/ImportDevice.cs new file mode 100644 index 00000000..a0a9bfdf --- /dev/null +++ b/Disco.Models/BI/Device/ImportDevice.cs @@ -0,0 +1,40 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Disco.Models.BI.Device +{ + public class ImportDevice + { + [Required, StringLength(60)] + public string SerialNumber { get; set; } + + public int? DeviceModelId { get; set; } + + [Range(1, int.MaxValue, ErrorMessage = "A valid Device Profile is Required")] + public int DeviceProfileId { get; set; } + + public int? DeviceBatchId { get; set; } + + [StringLength(50)] + public string AssignedUserId { get; set; } + + [StringLength(250)] + public string Location { get; set; } + + [StringLength(40)] + public string AssetNumber { get; set; } + + + public Repository.Device Device { get; set; } + public Repository.DeviceModel DeviceModel { get; set; } + public Repository.DeviceProfile DeviceProfile { get; set; } + public Repository.DeviceBatch DeviceBatch { get; set; } + public Repository.User AssignedUser { get; set; } + + public Dictionary Errors { get; set; } + } +} diff --git a/Disco.Models/Disco.Models.csproj b/Disco.Models/Disco.Models.csproj index a321be92..3e22754e 100644 --- a/Disco.Models/Disco.Models.csproj +++ b/Disco.Models/Disco.Models.csproj @@ -46,6 +46,7 @@ + @@ -123,6 +124,7 @@ + diff --git a/Disco.Models/Properties/AssemblyInfo.cs b/Disco.Models/Properties/AssemblyInfo.cs index 14340436..4bb2f882 100644 --- a/Disco.Models/Properties/AssemblyInfo.cs +++ b/Disco.Models/Properties/AssemblyInfo.cs @@ -32,5 +32,5 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.2.0722.1715")] -[assembly: AssemblyFileVersion("1.2.0722.1715")] \ No newline at end of file +[assembly: AssemblyVersion("1.2.0722.2112")] +[assembly: AssemblyFileVersion("1.2.0722.2112")] \ No newline at end of file diff --git a/Disco.Models/UI/Device/DeviceImportModel.cs b/Disco.Models/UI/Device/DeviceImportModel.cs new file mode 100644 index 00000000..fcd5a4cc --- /dev/null +++ b/Disco.Models/UI/Device/DeviceImportModel.cs @@ -0,0 +1,12 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Disco.Models.UI.Device +{ + public interface DeviceImportModel : BaseUIModel + { + } +} diff --git a/Disco.Services/Plugins/PluginManifest.cs b/Disco.Services/Plugins/PluginManifest.cs index ca3c4d6a..d6f16ade 100644 --- a/Disco.Services/Plugins/PluginManifest.cs +++ b/Disco.Services/Plugins/PluginManifest.cs @@ -143,6 +143,7 @@ namespace Disco.Services.Plugins "DotNet.Highcharts", "EntityFramework", "itextsharp", + "LumenWorks.Framework.IO", "Microsoft.AspNet.SignalR.Core", "Microsoft.AspNet.SignalR.Owin", "Microsoft.AspNet.SignalR.SystemWeb", diff --git a/Disco.Services/Properties/AssemblyInfo.cs b/Disco.Services/Properties/AssemblyInfo.cs index 7d4b100a..e5793b25 100644 --- a/Disco.Services/Properties/AssemblyInfo.cs +++ b/Disco.Services/Properties/AssemblyInfo.cs @@ -32,5 +32,5 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.2.0722.1715")] -[assembly: AssemblyFileVersion("1.2.0722.1715")] \ No newline at end of file +[assembly: AssemblyVersion("1.2.0722.2112")] +[assembly: AssemblyFileVersion("1.2.0722.2112")] \ No newline at end of file diff --git a/Disco.Web.Extensions/Properties/AssemblyInfo.cs b/Disco.Web.Extensions/Properties/AssemblyInfo.cs index 46179a77..01d5981f 100644 --- a/Disco.Web.Extensions/Properties/AssemblyInfo.cs +++ b/Disco.Web.Extensions/Properties/AssemblyInfo.cs @@ -32,5 +32,5 @@ using System.Runtime.InteropServices; // You can specify all the values or you can default the Build and Revision Numbers // by using the '*' as shown below: // [assembly: AssemblyVersion("1.0.*")] -[assembly: AssemblyVersion("1.2.0722.1715")] -[assembly: AssemblyFileVersion("1.2.0722.1715")] \ No newline at end of file +[assembly: AssemblyVersion("1.2.0722.2112")] +[assembly: AssemblyFileVersion("1.2.0722.2112")] \ No newline at end of file diff --git a/Disco.Web/Areas/API/Controllers/DeviceController.cs b/Disco.Web/Areas/API/Controllers/DeviceController.cs index 365a04df..dcea85ef 100644 --- a/Disco.Web/Areas/API/Controllers/DeviceController.cs +++ b/Disco.Web/Areas/API/Controllers/DeviceController.cs @@ -464,5 +464,27 @@ namespace Disco.Web.Areas.API.Controllers #endregion + public virtual ActionResult ImportParse(HttpPostedFileBase ImportFile) + { + if (ImportFile == null || ImportFile.ContentLength == 0) + throw new ArgumentNullException("ImportFile"); + + var status = Disco.BI.DeviceBI.Importing.ImportParseTask.Run(ImportFile.InputStream); + + status.SetFinishedUrl(Url.Action(MVC.API.Device.ImportProcess(status.SessionId))); + + return RedirectToAction(MVC.Config.Logging.TaskStatus(status.SessionId)); + } + + public virtual ActionResult ImportProcess(string ParseTaskSessionKey) + { + if (string.IsNullOrWhiteSpace(ParseTaskSessionKey)) + throw new ArgumentNullException("ParseTaskSessionKey"); + + var status = Disco.BI.DeviceBI.Importing.ImportProcessTask.Run(ParseTaskSessionKey); + + return RedirectToAction(MVC.Config.Logging.TaskStatus(status.SessionId)); + } + } } diff --git a/Disco.Web/Controllers/DeviceController.cs b/Disco.Web/Controllers/DeviceController.cs index a2ee9293..b6b8437f 100644 --- a/Disco.Web/Controllers/DeviceController.cs +++ b/Disco.Web/Controllers/DeviceController.cs @@ -64,7 +64,7 @@ namespace Disco.Web.Controllers if (!string.IsNullOrEmpty(m.Device.SerialNumber) && dbContext.Devices.Count(d => d.SerialNumber == m.Device.SerialNumber) > 0) ModelState.AddModelError("Device.SerialNumber", "A Device what this Serial Number already exists"); } - + if (ModelState.IsValid) { @@ -76,6 +76,19 @@ namespace Disco.Web.Controllers } #endregion + #region Import + [HttpGet] + public virtual ActionResult Import() + { + Models.Device.ImportModel m = new Models.Device.ImportModel(); + + // UI Extensions + UIExtensions.ExecuteExtensions(this.ControllerContext, m); + + return View(); + } + #endregion + #region Show public virtual ActionResult Show(string id) { @@ -118,7 +131,7 @@ namespace Disco.Web.Controllers HideClosedJobs = true, EnablePaging = false }; - m.Jobs.Fill(dbContext, BI.JobBI.Searching.BuildJobTableModel(dbContext).Where(j => j.DeviceSerialNumber == m.Device.SerialNumber).OrderByDescending(j => j.Id)); + m.Jobs.Fill(dbContext, BI.JobBI.Searching.BuildJobTableModel(dbContext).Where(j => j.DeviceSerialNumber == m.Device.SerialNumber).OrderByDescending(j => j.Id)); m.Certificates = dbContext.DeviceCertificates.Where(c => c.DeviceSerialNumber == m.Device.SerialNumber).ToList(); diff --git a/Disco.Web/Disco.Web.csproj b/Disco.Web/Disco.Web.csproj index b491c2bf..6be2127c 100644 --- a/Disco.Web/Disco.Web.csproj +++ b/Disco.Web/Disco.Web.csproj @@ -513,6 +513,7 @@ Global.asax + @@ -566,6 +567,11 @@ True _Subject.cshtml + + True + True + Import.cshtml + Index.cshtml True @@ -1444,6 +1450,10 @@ RazorGenerator _Subject.generated.cs + + RazorGenerator + Import.generated.cs + RazorGenerator Index.generated.cs @@ -1911,7 +1921,7 @@ False - + diff --git a/Disco.Web/Models/Device/ImportModel.cs b/Disco.Web/Models/Device/ImportModel.cs new file mode 100644 index 00000000..0cc92f84 --- /dev/null +++ b/Disco.Web/Models/Device/ImportModel.cs @@ -0,0 +1,15 @@ +using Disco.Models.UI.Device; +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Linq; +using System.Web; + +namespace Disco.Web.Models.Device +{ + public class ImportModel : DeviceImportModel + { + [Required, Display(Name="Import File")] + public HttpPostedFileBase ImportFile { get; set; } + } +} \ No newline at end of file diff --git a/Disco.Web/Properties/AssemblyInfo.cs b/Disco.Web/Properties/AssemblyInfo.cs index 06878c33..b5ab0d9c 100644 --- a/Disco.Web/Properties/AssemblyInfo.cs +++ b/Disco.Web/Properties/AssemblyInfo.cs @@ -31,5 +31,5 @@ using System.Runtime.InteropServices; // // You can specify all the values or you can default the Revision and Build Numbers // by using the '*' as shown below: -[assembly: AssemblyVersion("1.2.0722.1715")] -[assembly: AssemblyFileVersion("1.2.0722.1715")] \ No newline at end of file +[assembly: AssemblyVersion("1.2.0722.2112")] +[assembly: AssemblyFileVersion("1.2.0722.2112")] \ No newline at end of file diff --git a/Disco.Web/T4MVC.cs b/Disco.Web/T4MVC.cs index f289629d..dbc973de 100644 --- a/Disco.Web/T4MVC.cs +++ b/Disco.Web/T4MVC.cs @@ -1107,6 +1107,7 @@ namespace Disco.Web.Controllers { public readonly string Index = "Index"; public readonly string AddOffline = "AddOffline"; + public readonly string Import = "Import"; public readonly string Show = "Show"; } @@ -1115,6 +1116,7 @@ namespace Disco.Web.Controllers { public const string Index = "Index"; public const string AddOffline = "AddOffline"; + public const string Import = "Import"; public const string Show = "Show"; } @@ -1148,12 +1150,14 @@ namespace Disco.Web.Controllers public readonly string _DeviceTable = "_DeviceTable"; public readonly string _ViewStart = "_ViewStart"; public readonly string AddOffline = "AddOffline"; + public readonly string Import = "Import"; public readonly string Index = "Index"; public readonly string Show = "Show"; } public readonly string _DeviceTable = "~/Views/Device/_DeviceTable.cshtml"; public readonly string _ViewStart = "~/Views/Device/_ViewStart.cshtml"; public readonly string AddOffline = "~/Views/Device/AddOffline.cshtml"; + public readonly string Import = "~/Views/Device/Import.cshtml"; public readonly string Index = "~/Views/Device/Index.cshtml"; public readonly string Show = "~/Views/Device/Show.cshtml"; static readonly _DevicePartsClass s_DeviceParts = new _DevicePartsClass(); @@ -1213,6 +1217,15 @@ namespace Disco.Web.Controllers return callInfo; } + partial void ImportOverride(T4MVC_System_Web_Mvc_ActionResult callInfo); + + public override System.Web.Mvc.ActionResult Import() + { + var callInfo = new T4MVC_System_Web_Mvc_ActionResult(Area, Name, ActionNames.Import); + ImportOverride(callInfo); + return callInfo; + } + partial void ShowOverride(T4MVC_System_Web_Mvc_ActionResult callInfo, string id); public override System.Web.Mvc.ActionResult Show(string id) @@ -3268,6 +3281,18 @@ namespace Disco.Web.Areas.API.Controllers { return new T4MVC_System_Web_Mvc_ActionResult(Area, Name, ActionNames.AttachmentRemove); } + [NonAction] + [GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode] + public virtual System.Web.Mvc.ActionResult ImportParse() + { + return new T4MVC_System_Web_Mvc_ActionResult(Area, Name, ActionNames.ImportParse); + } + [NonAction] + [GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode] + public virtual System.Web.Mvc.ActionResult ImportProcess() + { + return new T4MVC_System_Web_Mvc_ActionResult(Area, Name, ActionNames.ImportProcess); + } [GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode] public DeviceController Actions { get { return MVC.API.Device; } } @@ -3302,6 +3327,8 @@ namespace Disco.Web.Areas.API.Controllers public readonly string Attachment = "Attachment"; public readonly string Attachments = "Attachments"; public readonly string AttachmentRemove = "AttachmentRemove"; + public readonly string ImportParse = "ImportParse"; + public readonly string ImportProcess = "ImportProcess"; } [GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode] @@ -3325,6 +3352,8 @@ namespace Disco.Web.Areas.API.Controllers public const string Attachment = "Attachment"; public const string Attachments = "Attachments"; public const string AttachmentRemove = "AttachmentRemove"; + public const string ImportParse = "ImportParse"; + public const string ImportProcess = "ImportProcess"; } @@ -3492,6 +3521,22 @@ namespace Disco.Web.Areas.API.Controllers { public readonly string id = "id"; } + static readonly ActionParamsClass_ImportParse s_params_ImportParse = new ActionParamsClass_ImportParse(); + [GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode] + public ActionParamsClass_ImportParse ImportParseParams { get { return s_params_ImportParse; } } + [GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode] + public class ActionParamsClass_ImportParse + { + public readonly string ImportFile = "ImportFile"; + } + static readonly ActionParamsClass_ImportProcess s_params_ImportProcess = new ActionParamsClass_ImportProcess(); + [GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode] + public ActionParamsClass_ImportProcess ImportProcessParams { get { return s_params_ImportProcess; } } + [GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode] + public class ActionParamsClass_ImportProcess + { + public readonly string ParseTaskSessionKey = "ParseTaskSessionKey"; + } static readonly ViewsClass s_views = new ViewsClass(); [GeneratedCode("T4MVC", "2.0"), DebuggerNonUserCode] public ViewsClass Views { get { return s_views; } } @@ -3711,6 +3756,26 @@ namespace Disco.Web.Areas.API.Controllers return callInfo; } + partial void ImportParseOverride(T4MVC_System_Web_Mvc_ActionResult callInfo, System.Web.HttpPostedFileBase ImportFile); + + public override System.Web.Mvc.ActionResult ImportParse(System.Web.HttpPostedFileBase ImportFile) + { + var callInfo = new T4MVC_System_Web_Mvc_ActionResult(Area, Name, ActionNames.ImportParse); + ModelUnbinderHelpers.AddRouteValues(callInfo.RouteValueDictionary, "ImportFile", ImportFile); + ImportParseOverride(callInfo, ImportFile); + return callInfo; + } + + partial void ImportProcessOverride(T4MVC_System_Web_Mvc_ActionResult callInfo, string ParseTaskSessionKey); + + public override System.Web.Mvc.ActionResult ImportProcess(string ParseTaskSessionKey) + { + var callInfo = new T4MVC_System_Web_Mvc_ActionResult(Area, Name, ActionNames.ImportProcess); + ModelUnbinderHelpers.AddRouteValues(callInfo.RouteValueDictionary, "ParseTaskSessionKey", ParseTaskSessionKey); + ImportProcessOverride(callInfo, ParseTaskSessionKey); + return callInfo; + } + } } diff --git a/Disco.Web/Views/Device/Import.cshtml b/Disco.Web/Views/Device/Import.cshtml new file mode 100644 index 00000000..6da62e59 --- /dev/null +++ b/Disco.Web/Views/Device/Import.cshtml @@ -0,0 +1,23 @@ +@model Disco.Web.Models.Device.ImportModel +@{ + ViewBag.Title = Html.ToBreadcrumb("Devices", MVC.Device.Index(), "Import Devices"); +} +@using (Html.BeginForm(MVC.API.Device.ImportParse(), FormMethod.Post, new { enctype = "multipart/form-data" })) +{ + @Html.ValidationSummary() +
+ + + + + +
+ @Html.LabelFor(m => m.ImportFile) + + +
+

+ +

+
+} \ No newline at end of file diff --git a/Disco.Web/Views/Device/Import.generated.cs b/Disco.Web/Views/Device/Import.generated.cs new file mode 100644 index 00000000..cb8c4ffb --- /dev/null +++ b/Disco.Web/Views/Device/Import.generated.cs @@ -0,0 +1,129 @@ +#pragma warning disable 1591 +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.18051 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace Disco.Web.Views.Device +{ + using System; + using System.Collections.Generic; + using System.IO; + using System.Linq; + using System.Net; + using System.Text; + using System.Web; + using System.Web.Helpers; + using System.Web.Mvc; + using System.Web.Mvc.Ajax; + using System.Web.Mvc.Html; + using System.Web.Routing; + using System.Web.Security; + using System.Web.UI; + using System.Web.WebPages; + using Disco.BI.Extensions; + using Disco.Models.Repository; + using Disco.Web; + using Disco.Web.Extensions; + + [System.CodeDom.Compiler.GeneratedCodeAttribute("RazorGenerator", "2.0.0.0")] + [System.Web.WebPages.PageVirtualPathAttribute("~/Views/Device/Import.cshtml")] + public partial class Import : System.Web.Mvc.WebViewPage + { + public Import() + { + } + public override void Execute() + { + + #line 2 "..\..\Views\Device\Import.cshtml" + + ViewBag.Title = Html.ToBreadcrumb("Devices", MVC.Device.Index(), "Import Devices"); + + + #line default + #line hidden +WriteLiteral("\r\n"); + + + #line 5 "..\..\Views\Device\Import.cshtml" + using (Html.BeginForm(MVC.API.Device.ImportParse(), FormMethod.Post, new { enctype = "multipart/form-data" })) +{ + + + #line default + #line hidden + + #line 7 "..\..\Views\Device\Import.cshtml" +Write(Html.ValidationSummary()); + + + #line default + #line hidden + + #line 7 "..\..\Views\Device\Import.cshtml" + + + + #line default + #line hidden +WriteLiteral(" \r\n \r\n \r\n \r\n \r\n \r\n
\r\n"); + +WriteLiteral(" "); + + + #line 12 "..\..\Views\Device\Import.cshtml" + Write(Html.LabelFor(m => m.ImportFile)); + + + #line default + #line hidden +WriteLiteral("\r\n \r\n \r\n
\r\n \r\n \r\n

\r\n \r\n"); + + + #line 23 "..\..\Views\Device\Import.cshtml" +} + + #line default + #line hidden + } + } +} +#pragma warning restore 1591 diff --git a/Disco.Web/Views/Device/Index.cshtml b/Disco.Web/Views/Device/Index.cshtml index 5831ad56..f6ba309d 100644 --- a/Disco.Web/Views/Device/Index.cshtml +++ b/Disco.Web/Views/Device/Index.cshtml @@ -5,5 +5,6 @@ Search for a Device @Html.Partial(MVC.Shared.Views._SearchDialog, "devices")
+ @Html.ActionLinkButton("Import Devices", MVC.Device.Import()) @Html.ActionLinkButton("Add Offline Device", MVC.Device.AddOffline())
diff --git a/Disco.Web/Views/Device/Index.generated.cs b/Disco.Web/Views/Device/Index.generated.cs index ca22fc55..7931ab50 100644 --- a/Disco.Web/Views/Device/Index.generated.cs +++ b/Disco.Web/Views/Device/Index.generated.cs @@ -2,7 +2,7 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. -// Runtime Version:4.0.30319.18033 +// Runtime Version:4.0.30319.18051 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -67,6 +67,17 @@ WriteLiteral(" "); #line 8 "..\..\Views\Device\Index.cshtml" +Write(Html.ActionLinkButton("Import Devices", MVC.Device.Import())); + + + #line default + #line hidden +WriteLiteral("\r\n"); + +WriteLiteral(" "); + + + #line 9 "..\..\Views\Device\Index.cshtml" Write(Html.ActionLinkButton("Add Offline Device", MVC.Device.AddOffline()));