diff --git a/Disco.Models/Disco.Models.csproj b/Disco.Models/Disco.Models.csproj index 82d54838..b43b6f5d 100644 --- a/Disco.Models/Disco.Models.csproj +++ b/Disco.Models/Disco.Models.csproj @@ -60,6 +60,9 @@ + + + diff --git a/Disco.Models/Services/Devices/Exporting/DeviceExportFieldMetadata.cs b/Disco.Models/Services/Devices/Exporting/DeviceExportFieldMetadata.cs new file mode 100644 index 00000000..fd8384cc --- /dev/null +++ b/Disco.Models/Services/Devices/Exporting/DeviceExportFieldMetadata.cs @@ -0,0 +1,21 @@ +using System; + +namespace Disco.Models.Services.Devices.Exporting +{ + public class DeviceExportFieldMetadata + { + public string Name { get; set; } + public string ColumnName { get; set; } + public Type ValueType { get; set; } + public Func Accessor { get; set; } + public Func CsvEncoder { get; set; } + + public DeviceExportFieldMetadata(string Name, Type ValueType, Func Accessor, Func CsvEncoder) + { + this.Name = Name; + this.ValueType = ValueType; + this.Accessor = Accessor; + this.CsvEncoder = CsvEncoder; + } + } +} diff --git a/Disco.Models/Services/Devices/Exporting/DeviceExportOptions.cs b/Disco.Models/Services/Devices/Exporting/DeviceExportOptions.cs index f18657e8..aebb3e30 100644 --- a/Disco.Models/Services/Devices/Exporting/DeviceExportOptions.cs +++ b/Disco.Models/Services/Devices/Exporting/DeviceExportOptions.cs @@ -12,10 +12,7 @@ namespace Disco.Models.Services.Devices.Exporting public DeviceExportTypes ExportType { get; set; } public int? ExportTypeTargetId { get; set; } - /// - /// Adds '=' to the beginning of the string to stop Excel removing the leading zeros - /// - public bool ExcelCsvFormat { get; set; } + public bool ExcelFormat { get; set; } // Device [Display(ShortName = "Device", Name = "Serial Number", Description = "The device serial number")] @@ -135,7 +132,7 @@ namespace Disco.Models.Services.Devices.Exporting return new DeviceExportOptions() { ExportType = DeviceExportTypes.All, - ExcelCsvFormat = true, + ExcelFormat = true, DeviceSerialNumber = true, ModelId = true, ProfileId = true, diff --git a/Disco.Models/Services/Devices/Exporting/DeviceExportResult.cs b/Disco.Models/Services/Devices/Exporting/DeviceExportResult.cs index 9af201f2..63ec1d3a 100644 --- a/Disco.Models/Services/Devices/Exporting/DeviceExportResult.cs +++ b/Disco.Models/Services/Devices/Exporting/DeviceExportResult.cs @@ -4,7 +4,7 @@ namespace Disco.Models.Services.Devices.Exporting { public class DeviceExportResult { - public MemoryStream CsvResult { get; set; } + public MemoryStream Result { get; set; } public int RecordCount { get; set; } } } \ No newline at end of file diff --git a/Disco.Models/Services/Devices/Importing/IDeviceImportColumn.cs b/Disco.Models/Services/Devices/Importing/IDeviceImportColumn.cs new file mode 100644 index 00000000..59e4ff27 --- /dev/null +++ b/Disco.Models/Services/Devices/Importing/IDeviceImportColumn.cs @@ -0,0 +1,14 @@ +using System; + +namespace Disco.Models.Services.Devices.Importing +{ + public interface IDeviceImportColumn + { + int Index { get; } + string Name { get; } + DeviceImportFieldTypes Type { get; } + Type Handler { get; } + + IDeviceImportField GetHandlerInstance(); + } +} diff --git a/Disco.Models/Services/Devices/Importing/IDeviceImportContext.cs b/Disco.Models/Services/Devices/Importing/IDeviceImportContext.cs index 3d761af2..cf93c1e1 100644 --- a/Disco.Models/Services/Devices/Importing/IDeviceImportContext.cs +++ b/Disco.Models/Services/Devices/Importing/IDeviceImportContext.cs @@ -1,8 +1,5 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Disco.Models.Services.Devices.Importing { @@ -10,11 +7,19 @@ namespace Disco.Models.Services.Devices.Importing { string SessionId { get; } string Filename { get; } - List> Header { get; } - List, Type>> ParsedHeaders { get; } - List RawData { get; } + string DatasetName { get; } + int ColumnCount { get; } + IEnumerable Columns { get; } + IDeviceImportColumn GetColumn(int Index); + void SetColumnType(int Index, DeviceImportFieldTypes Type); + int? GetColumnByType(DeviceImportFieldTypes FieldType); - List Records { get; } - int AffectedRecords { get; } + int RecordCount { get; } + + IDeviceImportDataReader GetDataReader(); + IEnumerable> GetFieldHandlers(); + + List Records { get; set; } + int AffectedRecords { get; set; } } } diff --git a/Disco.Models/Services/Devices/Importing/IDeviceImportDataReader.cs b/Disco.Models/Services/Devices/Importing/IDeviceImportDataReader.cs new file mode 100644 index 00000000..070d824b --- /dev/null +++ b/Disco.Models/Services/Devices/Importing/IDeviceImportDataReader.cs @@ -0,0 +1,33 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace Disco.Models.Services.Devices.Importing +{ + public interface IDeviceImportDataReader : IDisposable + { + void Reset(); + bool Read(); + + int Index { get; } + + int GetRowNumber(int Index); + + string GetString(int ColumnIndex); + IEnumerable GetStrings(int ColumnIndex); + + bool TryGetNullableInt(int ColumnIndex, out int? value); + + bool TryGetNullableBool(int ColumnIndex, out bool? value); + + bool TryGetNullableDateTime(int ColumnIndex, out DateTime? value); + + bool TestAllNotEmpty(int ColumnIndex); + bool TestAllNullableInt(int ColumnIndex); + bool TestAllInt(int ColumnIndex); + bool TestAllNullableBool(int ColumnIndex); + bool TestAllNullableDateTime(int ColumnIndex); + } +} diff --git a/Disco.Models/Services/Devices/Importing/IDeviceImportField.cs b/Disco.Models/Services/Devices/Importing/IDeviceImportField.cs index b76f01d3..1274a820 100644 --- a/Disco.Models/Services/Devices/Importing/IDeviceImportField.cs +++ b/Disco.Models/Services/Devices/Importing/IDeviceImportField.cs @@ -1,9 +1,4 @@ -using System; -using System.Collections.Generic; -using System.Data; -using System.Linq; -using System.Text; -using System.Threading.Tasks; +using System.Data; namespace Disco.Models.Services.Devices.Importing { diff --git a/Disco.Models/Services/Devices/Importing/IDeviceImportRecord.cs b/Disco.Models/Services/Devices/Importing/IDeviceImportRecord.cs index 34430c41..0af19d32 100644 --- a/Disco.Models/Services/Devices/Importing/IDeviceImportRecord.cs +++ b/Disco.Models/Services/Devices/Importing/IDeviceImportRecord.cs @@ -9,6 +9,7 @@ namespace Disco.Models.Services.Devices.Importing { public interface IDeviceImportRecord { + int Index { get; } string DeviceSerialNumber { get; } IEnumerable Fields { get; } diff --git a/Disco.Services/Devices/Exporting/DeviceExport.cs b/Disco.Services/Devices/Exporting/DeviceExport.cs index f98e2515..ef25fa71 100644 --- a/Disco.Services/Devices/Exporting/DeviceExport.cs +++ b/Disco.Services/Devices/Exporting/DeviceExport.cs @@ -1,4 +1,5 @@ -using Disco.Data.Repository; +using ClosedXML.Excel; +using Disco.Data.Repository; using Disco.Models.Repository; using Disco.Models.Services.Devices.Exporting; using Disco.Services.Tasks; @@ -6,10 +7,10 @@ using Disco.Services.Users; using System; using System.Collections.Generic; using System.ComponentModel.DataAnnotations; +using System.Data; using System.IO; using System.Linq; using System.Text; -using System.Threading.Tasks; namespace Disco.Services.Devices.Exporting { @@ -52,7 +53,7 @@ namespace Disco.Services.Devices.Exporting try { TaskStatus.IgnoreCurrentProcessChanges = true; - TaskStatus.ProgressMultiplier = 20 / 100; + TaskStatus.ProgressMultiplier = (double)20 / 100; TaskStatus.ProgressOffset = 40; Interop.ActiveDirectory.ADNetworkLogonDatesUpdateTask.UpdateLastNetworkLogonDates(Database, TaskStatus); @@ -73,41 +74,23 @@ namespace Disco.Services.Devices.Exporting TaskStatus.UpdateStatus(80, string.Format("Formatting {0} records for export", records.Count)); - using (StreamWriter writer = new StreamWriter(stream, Encoding.Default, 0x400, true)) + if (Options.ExcelFormat) { - // Header - writer.Write('"'); - writer.Write(string.Join("\",\"", metadata.Select(m => m.Item2))); - writer.Write('"'); - - // Records - foreach (var record in records) - { - writer.WriteLine(); - writer.Write(string.Join(",", metadata.Select(m => - { - var value = m.Item3(record); - var isString = m.Item4; - - if (value == null) - return null; - else if (!isString) - return value; - else if (Options.ExcelCsvFormat) - return string.Concat("=\"", value, "\""); - else - return string.Concat("\"", value, "\""); - }))); - } + WriteXlsx(stream, metadata, records); + } + else + { + WriteCSV(stream, metadata, records); } stream.Position = 0; return new DeviceExportResult() { - CsvResult = stream, + Result = stream, RecordCount = records.Count }; } + public static DeviceExportResult GenerateExport(DiscoDataContext Database, IQueryable Devices, DeviceExportOptions Options) { return GenerateExport(Database, Devices, Options, ScheduledTaskMockStatus.Create("Device Export")); @@ -137,6 +120,57 @@ namespace Disco.Services.Devices.Exporting return GenerateExport(Database, Options, ScheduledTaskMockStatus.Create("Device Export")); } + private static void WriteCSV(Stream OutputStream, List Metadata, List Records) + { + using (StreamWriter writer = new StreamWriter(OutputStream, Encoding.Default, 0x400, true)) + { + // Header + writer.Write('"'); + writer.Write(string.Join("\",\"", Metadata.Select(m => m.ColumnName))); + writer.Write('"'); + + // Records + foreach (var record in Records) + { + writer.WriteLine(); + for (int i = 0; i < Metadata.Count; i++) + { + if (i != 0) + { + writer.Write(','); + } + var value = Metadata[i].Accessor(record); + writer.Write(Metadata[i].CsvEncoder(value)); + } + } + } + } + + private static void WriteXlsx(Stream OutputStream, List Metadata, List Records) + { + // Create DataTable + var dataTable = new DataTable(); + foreach (var field in Metadata) + { + dataTable.Columns.Add(field.ColumnName, field.ValueType); + } + foreach (var record in Records) + { + dataTable.Rows.Add(Metadata.Select(m => m.Accessor(record)).ToArray()); + } + + using (var xlWorkbook = new XLWorkbook()) + { + var sheet = xlWorkbook.AddWorksheet("DeviceExport"); + var table = sheet.Cell(1, 1).InsertTable(dataTable, "Devices"); + table.Theme = XLTableTheme.TableStyleMedium2; + + table.Columns().ForEach(c => c.WorksheetColumn().AdjustToContents(2, 15, 30)); + + xlWorkbook.SaveAs(OutputStream); + } + } + private static IEnumerable BuildRecords(IQueryable Devices) { var deviceDetailHardwareKeys = new List { @@ -185,93 +219,103 @@ namespace Disco.Services.Devices.Exporting }); } - /// Tuple Format: Property Name, Column Name, Property Access, Escape CSV Value? - private static List, bool>> BuildMetadata(this DeviceExportOptions Options) + private static List BuildMetadata(this DeviceExportOptions Options) { - var allAssessors = BuildRecordAssessors().ToList(); + var allAssessors = BuildRecordAccessors().ToList(); return typeof(DeviceExportOptions).GetProperties() .Where(p => p.PropertyType == typeof(bool)) - .Select(p => Tuple.Create(p, (DisplayAttribute)p.GetCustomAttributes(typeof(DisplayAttribute), false).FirstOrDefault())) - .Where(p => p.Item2 != null && (bool)p.Item1.GetValue(Options)) + .Select(p => new + { + property = p, + details = (DisplayAttribute)p.GetCustomAttributes(typeof(DisplayAttribute), false).FirstOrDefault() + }) + .Where(p => p.details != null && (bool)p.property.GetValue(Options)) .Select(p => { - var accessor = allAssessors.First(i => i.Item1 == p.Item1.Name); - var columnName = (p.Item2.ShortName == "Device" || p.Item2.ShortName == "Details") ? p.Item2.Name : string.Format("{0} {1}", p.Item2.ShortName, p.Item2.Name); - return Tuple.Create(p.Item1.Name, columnName, accessor.Item2, accessor.Item3); + var fieldMetadata = allAssessors.First(i => i.Name == p.property.Name); + fieldMetadata.ColumnName = (p.details.ShortName == "Device" || p.details.ShortName == "Details") ? p.details.Name : $"{p.details.ShortName} {p.details.Name}"; + return fieldMetadata; }).ToList(); } - /// Tuple Format: Property Name, Property Access, Escape CSV Value? - private static IEnumerable, bool>> BuildRecordAssessors() + private static IEnumerable BuildRecordAccessors() { const string DateFormat = "yyyy-MM-dd"; const string DateTimeFormat = DateFormat + " HH:mm:ss"; + Func csvStringEncoded = (o) => o == null ? null : $"\"{((string)o).Replace("\"", "\"\"")}\""; + Func csvToStringEncoded = (o) => o == null ? null : o.ToString(); + Func csvCurrencyEncoded = (o) => ((decimal?)o).HasValue ? ((decimal?)o).Value.ToString("C") : null; + Func csvDateEncoded = (o) => ((DateTime)o).ToString(DateFormat); + Func csvDateTimeEncoded = (o) => ((DateTime)o).ToString(DateTimeFormat); + Func csvNullableDateEncoded = (o) => ((DateTime?)o).HasValue ? csvDateEncoded(o) : null; + Func csvNullableDateTimeEncoded = (o) => ((DateTime?)o).HasValue ? csvDateTimeEncoded(o) : null; + // Device - yield return new Tuple, bool>("DeviceSerialNumber", r => r.Device.SerialNumber, true); - yield return new Tuple, bool>("DeviceAssetNumber", r => r.Device.AssetNumber, true); - yield return new Tuple, bool>("DeviceLocation", r => r.Device.Location, true); - yield return new Tuple, bool>("DeviceComputerName", r => r.Device.DeviceDomainId, true); - yield return new Tuple, bool>("DeviceLastNetworkLogon", r => r.Device.LastNetworkLogonDate.HasValue ? r.Device.LastNetworkLogonDate.Value.ToString(DateTimeFormat) : null, false); - yield return new Tuple, bool>("DeviceCreatedDate", r => r.Device.CreatedDate.ToString(DateTimeFormat), false); - yield return new Tuple, bool>("DeviceFirstEnrolledDate", r => r.Device.EnrolledDate.HasValue ? r.Device.EnrolledDate.Value.ToString(DateTimeFormat) : null, false); - yield return new Tuple, bool>("DeviceLastEnrolledDate", r => r.Device.LastEnrolDate.HasValue ? r.Device.LastEnrolDate.Value.ToString(DateTimeFormat) : null, false); - yield return new Tuple, bool>("DeviceAllowUnauthenticatedEnrol", r => r.Device.AllowUnauthenticatedEnrol.ToString(), false); - yield return new Tuple, bool>("DeviceDecommissionedDate", r => r.Device.DecommissionedDate.HasValue ? r.Device.DecommissionedDate.Value.ToString(DateTimeFormat) : null, false); - yield return new Tuple, bool>("DeviceDecommissionedReason", r => r.Device.DecommissionReason.HasValue ? r.Device.DecommissionReason.Value.ToString() : null, true); + yield return new DeviceExportFieldMetadata("DeviceSerialNumber", typeof(string), r => r.Device.SerialNumber, csvStringEncoded); + yield return new DeviceExportFieldMetadata("DeviceAssetNumber", typeof(string), r => r.Device.AssetNumber, csvStringEncoded); + yield return new DeviceExportFieldMetadata("DeviceLocation", typeof(string), r => r.Device.Location, csvStringEncoded); + yield return new DeviceExportFieldMetadata("DeviceComputerName", typeof(string), r => r.Device.DeviceDomainId, csvStringEncoded); + yield return new DeviceExportFieldMetadata("DeviceLastNetworkLogon", typeof(DateTime), r => r.Device.LastNetworkLogonDate, csvNullableDateTimeEncoded); + yield return new DeviceExportFieldMetadata("DeviceCreatedDate", typeof(DateTime), r => r.Device.CreatedDate, csvDateTimeEncoded); + yield return new DeviceExportFieldMetadata("DeviceFirstEnrolledDate", typeof(DateTime), r => r.Device.EnrolledDate, csvNullableDateTimeEncoded); + yield return new DeviceExportFieldMetadata("DeviceLastEnrolledDate", typeof(DateTime), r => r.Device.LastEnrolDate, csvNullableDateTimeEncoded); + yield return new DeviceExportFieldMetadata("DeviceAllowUnauthenticatedEnrol", typeof(bool), r => r.Device.AllowUnauthenticatedEnrol, csvToStringEncoded); + yield return new DeviceExportFieldMetadata("DeviceDecommissionedDate", typeof(DateTime), r => r.Device.DecommissionedDate, csvNullableDateTimeEncoded); + yield return new DeviceExportFieldMetadata("DeviceDecommissionedReason", typeof(string), r => r.Device.DecommissionReason, csvToStringEncoded); // Details - yield return new Tuple, bool>("DetailLanMacAddress", r => r.DeviceDetails.Where(dd => dd.Key == DeviceDetail.HardwareKeyLanMacAddress).Select(dd => dd.Value).FirstOrDefault(), true); - yield return new Tuple, bool>("DetailWLanMacAddress", r => r.DeviceDetails.Where(dd => dd.Key == DeviceDetail.HardwareKeyWLanMacAddress).Select(dd => dd.Value).FirstOrDefault(), true); - yield return new Tuple, bool>("DetailACAdapter", r => r.DeviceDetails.Where(dd => dd.Key == DeviceDetail.HardwareKeyACAdapter).Select(dd => dd.Value).FirstOrDefault(), true); - yield return new Tuple, bool>("DetailBattery", r => r.DeviceDetails.Where(dd => dd.Key == DeviceDetail.HardwareKeyBattery).Select(dd => dd.Value).FirstOrDefault(), true); - yield return new Tuple, bool>("DetailKeyboard", r => r.DeviceDetails.Where(dd => dd.Key == DeviceDetail.HardwareKeyKeyboard).Select(dd => dd.Value).FirstOrDefault(), true); + yield return new DeviceExportFieldMetadata("DetailLanMacAddress", typeof(string), r => r.DeviceDetails.Where(dd => dd.Key == DeviceDetail.HardwareKeyLanMacAddress).Select(dd => dd.Value).FirstOrDefault(), csvStringEncoded); + yield return new DeviceExportFieldMetadata("DetailWLanMacAddress", typeof(string), r => r.DeviceDetails.Where(dd => dd.Key == DeviceDetail.HardwareKeyWLanMacAddress).Select(dd => dd.Value).FirstOrDefault(), csvStringEncoded); + yield return new DeviceExportFieldMetadata("DetailACAdapter", typeof(string), r => r.DeviceDetails.Where(dd => dd.Key == DeviceDetail.HardwareKeyACAdapter).Select(dd => dd.Value).FirstOrDefault(), csvStringEncoded); + yield return new DeviceExportFieldMetadata("DetailBattery", typeof(string), r => r.DeviceDetails.Where(dd => dd.Key == DeviceDetail.HardwareKeyBattery).Select(dd => dd.Value).FirstOrDefault(), csvStringEncoded); + yield return new DeviceExportFieldMetadata("DetailKeyboard", typeof(string), r => r.DeviceDetails.Where(dd => dd.Key == DeviceDetail.HardwareKeyKeyboard).Select(dd => dd.Value).FirstOrDefault(), csvStringEncoded); // Model - yield return new Tuple, bool>("ModelId", r => r.ModelId.HasValue ? r.ModelId.Value.ToString() : null, false); - yield return new Tuple, bool>("ModelDescription", r => r.ModelDescription, true); - yield return new Tuple, bool>("ModelManufacturer", r => r.ModelManufacturer, true); - yield return new Tuple, bool>("ModelModel", r => r.ModelModel, true); - yield return new Tuple, bool>("ModelType", r => r.ModelType, true); + yield return new DeviceExportFieldMetadata("ModelId", typeof(int), r => r.ModelId, csvToStringEncoded); + yield return new DeviceExportFieldMetadata("ModelDescription", typeof(string), r => r.ModelDescription, csvStringEncoded); + yield return new DeviceExportFieldMetadata("ModelManufacturer", typeof(string), r => r.ModelManufacturer, csvStringEncoded); + yield return new DeviceExportFieldMetadata("ModelModel", typeof(string), r => r.ModelModel, csvStringEncoded); + yield return new DeviceExportFieldMetadata("ModelType", typeof(string), r => r.ModelType, csvStringEncoded); // Batch - yield return new Tuple, bool>("BatchId", r => r.BatchId.HasValue ? r.BatchId.Value.ToString() : null, false); - yield return new Tuple, bool>("BatchName", r => r.BatchName, true); - yield return new Tuple, bool>("BatchPurchaseDate", r => r.BatchPurchaseDate.HasValue ? r.BatchPurchaseDate.Value.ToString(DateFormat) : null, false); - yield return new Tuple, bool>("BatchSupplier", r => r.BatchSupplier, true); - yield return new Tuple, bool>("BatchUnitCost", r => r.BatchUnitCost.HasValue ? r.BatchUnitCost.ToString() : null, false); - yield return new Tuple, bool>("BatchWarrantyValidUntilDate", r => r.BatchWarrantyValidUntilDate.HasValue ? r.BatchWarrantyValidUntilDate.Value.ToString(DateFormat) : null, false); - yield return new Tuple, bool>("BatchInsuredDate", r => r.BatchInsuredDate.HasValue ? r.BatchInsuredDate.Value.ToString(DateFormat) : null, false); - yield return new Tuple, bool>("BatchInsuranceSupplier", r => r.BatchInsuranceSupplier, true); - yield return new Tuple, bool>("BatchInsuredUntilDate", r => r.BatchInsuredUntilDate.HasValue ? r.BatchInsuredUntilDate.Value.ToString(DateFormat) : null, false); + yield return new DeviceExportFieldMetadata("BatchId", typeof(int), r => r.BatchId, csvToStringEncoded); + yield return new DeviceExportFieldMetadata("BatchName", typeof(string), r => r.BatchName, csvStringEncoded); + yield return new DeviceExportFieldMetadata("BatchPurchaseDate", typeof(DateTime), r => r.BatchPurchaseDate, csvNullableDateEncoded); + yield return new DeviceExportFieldMetadata("BatchSupplier", typeof(string), r => r.BatchSupplier, csvStringEncoded); + yield return new DeviceExportFieldMetadata("BatchUnitCost", typeof(decimal), r => r.BatchUnitCost, csvCurrencyEncoded); + yield return new DeviceExportFieldMetadata("BatchWarrantyValidUntilDate", typeof(DateTime), r => r.BatchWarrantyValidUntilDate, csvNullableDateEncoded); + yield return new DeviceExportFieldMetadata("BatchInsuredDate", typeof(DateTime), r => r.BatchInsuredDate, csvNullableDateEncoded); + yield return new DeviceExportFieldMetadata("BatchInsuranceSupplier", typeof(string), r => r.BatchInsuranceSupplier, csvStringEncoded); + yield return new DeviceExportFieldMetadata("BatchInsuredUntilDate", typeof(DateTime), r => r.BatchInsuredUntilDate, csvNullableDateEncoded); // Profile - yield return new Tuple, bool>("ProfileId", r => r.ProfileId.ToString(), false); - yield return new Tuple, bool>("ProfileName", r => r.ProfileName, true); - yield return new Tuple, bool>("ProfileShortName", r => r.ProfileShortName, true); + yield return new DeviceExportFieldMetadata("ProfileId", typeof(int), r => r.ProfileId, csvToStringEncoded); + yield return new DeviceExportFieldMetadata("ProfileName", typeof(string), r => r.ProfileName, csvStringEncoded); + yield return new DeviceExportFieldMetadata("ProfileShortName", typeof(string), r => r.ProfileShortName, csvStringEncoded); // User - yield return new Tuple, bool>("AssignedUserId", r => r.AssignedUser != null ? r.AssignedUser.UserId : null, true); - yield return new Tuple, bool>("AssignedUserDate", r => r.DeviceUserAssignment != null ? r.DeviceUserAssignment.AssignedDate.ToString(DateTimeFormat) : null, false); - yield return new Tuple, bool>("AssignedUserDisplayName", r => r.AssignedUser != null ? r.AssignedUser.DisplayName : null, true); - yield return new Tuple, bool>("AssignedUserSurname", r => r.AssignedUser != null ? r.AssignedUser.Surname : null, true); - yield return new Tuple, bool>("AssignedUserGivenName", r => r.AssignedUser != null ? r.AssignedUser.GivenName : null, true); - yield return new Tuple, bool>("AssignedUserPhoneNumber", r => r.AssignedUser != null ? r.AssignedUser.PhoneNumber : null, true); - yield return new Tuple, bool>("AssignedUserEmailAddress", r => r.AssignedUser != null ? r.AssignedUser.EmailAddress : null, true); + yield return new DeviceExportFieldMetadata("AssignedUserId", typeof(string), r => r.AssignedUser?.UserId, csvStringEncoded); + yield return new DeviceExportFieldMetadata("AssignedUserDate", typeof(DateTime), r => r.DeviceUserAssignment?.AssignedDate, csvNullableDateTimeEncoded); + yield return new DeviceExportFieldMetadata("AssignedUserDisplayName", typeof(string), r => r.AssignedUser?.DisplayName, csvStringEncoded); + yield return new DeviceExportFieldMetadata("AssignedUserSurname", typeof(string), r => r.AssignedUser?.Surname, csvStringEncoded); + yield return new DeviceExportFieldMetadata("AssignedUserGivenName", typeof(string), r => r.AssignedUser?.GivenName, csvStringEncoded); + yield return new DeviceExportFieldMetadata("AssignedUserPhoneNumber", typeof(string), r => r.AssignedUser?.PhoneNumber, csvStringEncoded); + yield return new DeviceExportFieldMetadata("AssignedUserEmailAddress", typeof(string), r => r.AssignedUser?.EmailAddress, csvStringEncoded); // Jobs - yield return new Tuple, bool>("JobsTotalCount", r => r.JobsTotalCount.ToString(), false); - yield return new Tuple, bool>("JobsOpenCount", r => r.JobsOpenCount.ToString(), false); + yield return new DeviceExportFieldMetadata("JobsTotalCount", typeof(int), r => r.JobsTotalCount, csvToStringEncoded); + yield return new DeviceExportFieldMetadata("JobsOpenCount", typeof(int), r => r.JobsOpenCount, csvToStringEncoded); // Attachments - yield return new Tuple, bool>("AttachmentsCount", r => r.AttachmentsCount.ToString(), false); + yield return new DeviceExportFieldMetadata("AttachmentsCount", typeof(int), r => r.AttachmentsCount, csvToStringEncoded); // Certificates - yield return new Tuple, bool>("CertificateName", r => r.DeviceCertificate != null ? r.DeviceCertificate.Name : null, true); - yield return new Tuple, bool>("CertificateAllocatedDate", r => r.DeviceCertificate != null && r.DeviceCertificate.AllocatedDate.HasValue ? r.DeviceCertificate.AllocatedDate.Value.ToString(DateTimeFormat) : null, false); - yield return new Tuple, bool>("CertificateExpirationDate", r => r.DeviceCertificate != null && r.DeviceCertificate.ExpirationDate.HasValue ? r.DeviceCertificate.ExpirationDate.Value.ToString(DateTimeFormat) : null, false); - yield return new Tuple, bool>("CertificateProviderId", r => r.DeviceCertificate != null ? r.DeviceCertificate.ProviderId : null, true); + yield return new DeviceExportFieldMetadata("CertificateName", typeof(string), r => r.DeviceCertificate?.Name, csvStringEncoded); + yield return new DeviceExportFieldMetadata("CertificateAllocatedDate", typeof(DateTime), r => r.DeviceCertificate?.AllocatedDate, csvNullableDateTimeEncoded); + yield return new DeviceExportFieldMetadata("CertificateExpirationDate", typeof(DateTime), r => r.DeviceCertificate?.ExpirationDate, csvNullableDateTimeEncoded); + yield return new DeviceExportFieldMetadata("CertificateProviderId", typeof(string), r => r.DeviceCertificate?.ProviderId, csvStringEncoded); } } diff --git a/Disco.Services/Devices/Importing/BaseDeviceImportContext.cs b/Disco.Services/Devices/Importing/BaseDeviceImportContext.cs new file mode 100644 index 00000000..4387ca26 --- /dev/null +++ b/Disco.Services/Devices/Importing/BaseDeviceImportContext.cs @@ -0,0 +1,142 @@ +using Disco.Models.Services.Devices.Importing; +using Disco.Services.Devices.Importing.Fields; +using System; +using System.Collections.Generic; +using System.Linq; + +namespace Disco.Services.Devices.Importing +{ + public abstract class BaseDeviceImportContext : IDeviceImportContext + { + private static Lazy> FieldHandlers = new Lazy>(() => + { + return new Dictionary() + { + { DeviceImportFieldTypes.DeviceSerialNumber, typeof(DeviceSerialNumberImportField) }, + { DeviceImportFieldTypes.DeviceAssetNumber, typeof(DeviceAssetNumberImportField) }, + { DeviceImportFieldTypes.DeviceLocation, typeof(DeviceLocationImportField) }, + { DeviceImportFieldTypes.DeviceAllowUnauthenticatedEnrol, typeof(DeviceAllowUnauthenticatedEnrolImportField) }, + { DeviceImportFieldTypes.DeviceDecommissionedDate, typeof(DeviceDecommissionedDateImportField) }, + { DeviceImportFieldTypes.DeviceDecommissionedReason, typeof(DeviceDecommissionedReasonImportField) }, + + { DeviceImportFieldTypes.DetailLanMacAddress, typeof(DetailLanMacAddressImportField) }, + { DeviceImportFieldTypes.DetailWLanMacAddress, typeof(DetailWLanMacAddressImportField) }, + { DeviceImportFieldTypes.DetailACAdapter, typeof(DetailACAdapterImportField) }, + { DeviceImportFieldTypes.DetailBattery, typeof(DetailBatteryImportField) }, + { DeviceImportFieldTypes.DetailKeyboard, typeof(DetailKeyboardImportField) }, + + { DeviceImportFieldTypes.ModelId, typeof(ModelIdImportField) }, + + { DeviceImportFieldTypes.BatchId, typeof(BatchIdImportField) }, + + { DeviceImportFieldTypes.ProfileId, typeof(ProfileIdImportField) }, + + { DeviceImportFieldTypes.AssignedUserId, typeof(AssignedUserIdImportField) } + }; + }); + + private List columns; + private Dictionary columnsByType; + + public string SessionId { get; } + public string Filename { get; } + public string DatasetName { get; private set; } + + public abstract int RecordCount { get; } + + public List Records { get; set; } + public int AffectedRecords { get; set; } + + public int ColumnCount { get { return columns.Count; } } + public IEnumerable Columns + { + get + { + if (columns == null) + throw new ArgumentNullException(nameof(columns)); + + return columns; + } + } + + public BaseDeviceImportContext(string Filename) + { + SessionId = Guid.NewGuid().ToString("D"); + this.Filename = DatasetName = string.IsNullOrWhiteSpace(Filename) ? "" : Filename; + } + + public abstract IDeviceImportDataReader GetDataReader(); + + public IDeviceImportColumn GetColumn(int Index) + { + if (columns == null) + throw new ArgumentNullException(nameof(columns)); + + return columns[Index]; + } + + public void SetColumnType(int Index, DeviceImportFieldTypes Type) + { + if (columns == null) + throw new ArgumentNullException(nameof(columns)); + + var column = columns[Index]; + + if (column.Type == Type) + { + return; // No change + } + + if (column.Type != DeviceImportFieldTypes.IgnoreColumn) + { + columnsByType.Remove(column.Type); + } + + column.Type = Type; + + if (Type == DeviceImportFieldTypes.IgnoreColumn) + { + column.Handler = null; + } + else + { + columnsByType[Type] = column; + column.Handler = FieldHandlers.Value[Type]; + } + } + + public int? GetColumnByType(DeviceImportFieldTypes FieldType) + { + if (columnsByType == null) + throw new ArgumentNullException(nameof(columnsByType)); + + DeviceImportColumn column; + if (columnsByType.TryGetValue(FieldType, out column)) + { + return column.Index; + } + else + { + return null; + } + } + + protected void SetDatasetName(string DatasetName) + { + this.DatasetName = DatasetName; + } + + protected void SetColumns(IEnumerable Columns) + { + columns = Columns.ToList(); + columnsByType = columns + .Where(c => c.Type != DeviceImportFieldTypes.IgnoreColumn) + .ToDictionary(c => c.Type); + } + + public IEnumerable> GetFieldHandlers() + { + return FieldHandlers.Value; + } + } +} diff --git a/Disco.Services/Devices/Importing/CsvDeviceImportContext.cs b/Disco.Services/Devices/Importing/CsvDeviceImportContext.cs new file mode 100644 index 00000000..8df6ba67 --- /dev/null +++ b/Disco.Services/Devices/Importing/CsvDeviceImportContext.cs @@ -0,0 +1,56 @@ +using Disco.Models.Services.Devices.Importing; +using LumenWorks.Framework.IO.Csv; +using System.Collections.Generic; +using System.Data; +using System.IO; +using System.Linq; + +namespace Disco.Services.Devices.Importing +{ + public class CsvDeviceImportContext : BaseDeviceImportContext + { + private List rawData; + private bool hasHeaderRow; + + public CsvDeviceImportContext(string Filename, bool HasHeader, Stream CsvStream) + : base(Filename) + { + hasHeaderRow = HasHeader; + + ParseCsv(CsvStream); + } + + public override int RecordCount + { + get + { + return rawData.Count; + } + } + + public override IDeviceImportDataReader GetDataReader() + { + return new CsvDeviceImportDataReader(this, rawData, hasHeaderRow); + } + + private void ParseCsv(Stream CsvStream) + { + using (TextReader csvTextReader = new StreamReader(CsvStream)) + { + using (CsvReader csvReader = new CsvReader(csvTextReader, hasHeaderRow)) + { + csvReader.DefaultParseErrorAction = ParseErrorAction.ThrowException; + csvReader.MissingFieldAction = MissingFieldAction.ReplaceByNull; + + rawData = csvReader.ToList(); + SetColumns(csvReader.GetFieldHeaders().Select((h, i) => new DeviceImportColumn() + { + Index = i, + Name = h, + Type = DeviceImportFieldTypes.IgnoreColumn + })); + } + } + } + } +} \ No newline at end of file diff --git a/Disco.Services/Devices/Importing/CsvDeviceImportDataReader.cs b/Disco.Services/Devices/Importing/CsvDeviceImportDataReader.cs new file mode 100644 index 00000000..31e36452 --- /dev/null +++ b/Disco.Services/Devices/Importing/CsvDeviceImportDataReader.cs @@ -0,0 +1,222 @@ +using Disco.Models.Services.Devices.Importing; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; + +namespace Disco.Services.Devices.Importing +{ + public class CsvDeviceImportDataReader : IDeviceImportDataReader + { + private static string[] TrueValues = { "true", "1", "yes", "-1", "on" }; + private static string[] FalseValues = { "false", "0", "no", "off" }; + + private CsvDeviceImportContext context; + private List rawData; + private int currentRowIndex; + private int rowOffset; + private string[] currentRow; + + public int Index { get { return currentRowIndex; } } + + public CsvDeviceImportDataReader(CsvDeviceImportContext Context, List RawData, bool HasHeaderRow) + { + context = Context; + rawData = RawData; + currentRowIndex = 0; + rowOffset = HasHeaderRow ? 1 : 0; + } + + public void Reset() + { + currentRowIndex = 0; + currentRow = null; + } + + public bool Read() + { + if (++currentRowIndex >= rawData.Count) + { + currentRowIndex--; + return false; + } + + currentRow = rawData[currentRowIndex]; + return true; + } + + public int GetRowNumber(int Index) + { + return Index + rowOffset; + } + + public string GetString(int ColumnIndex) + { + if (currentRow == null) + throw new InvalidOperationException($"{nameof(CsvDeviceImportDataReader.Read)} must be called before retrieving values"); + + var value = currentRow[ColumnIndex]; + + if (value.Length == 0) + return null; + + return value; + } + + public IEnumerable GetStrings(int ColumnIndex) + { + return rawData.Select(r => r[ColumnIndex]); + } + + public bool TryGetNullableInt(int ColumnIndex, out int? value) + { + if (currentRow == null) + throw new InvalidOperationException($"{nameof(CsvDeviceImportDataReader.Read)} must be called before retrieving values"); + + return TryGetNullableInt(currentRow[ColumnIndex], out value); + } + + public bool TryGetNullableBool(int ColumnIndex, out bool? value) + { + if (currentRow == null) + throw new InvalidOperationException($"{nameof(CsvDeviceImportDataReader.Read)} must be called before retrieving values"); + + return TryGetNullableBool(currentRow[ColumnIndex], out value); + } + + public bool TryGetNullableDateTime(int ColumnIndex, out DateTime? value) + { + if (currentRow == null) + throw new InvalidOperationException($"{nameof(CsvDeviceImportDataReader.Read)} must be called before retrieving values"); + + return TryGetNullableDateTime(currentRow[ColumnIndex], out value); + } + + public bool TestAllNotEmpty(int ColumnIndex) + { + return GetStrings(ColumnIndex).All(s => !string.IsNullOrWhiteSpace(s)); + } + + public bool TestAllNullableInt(int ColumnIndex) + { + return rawData.Select(r => r[ColumnIndex]) + .All(c => + { + int? value; + return TryGetNullableInt(c, out value); + }); + } + + public bool TestAllInt(int ColumnIndex) + { + return rawData.Select(r => r[ColumnIndex]) + .All(c => + { + int? value; + return TryGetNullableInt(c, out value) && value.HasValue; + }); + } + + public bool TestAllNullableBool(int ColumnIndex) + { + return rawData.Select(r => r[ColumnIndex]) + .All(c => + { + bool? value; + return TryGetNullableBool(c, out value); + }); + } + + public bool TestAllNullableDateTime(int ColumnIndex) + { + return rawData.Select(r => r[ColumnIndex]) + .All(c => + { + DateTime? value; + return TryGetNullableDateTime(c, out value); + }); + } + + public void Dispose() + { + // Nothing to dispose + } + + private bool TryGetNullableDateTime(string content, out DateTime? value) + { + if (string.IsNullOrWhiteSpace(content)) + { + value = null; + return true; + } + else + { + content = content.Trim(); + + DateTime valueDateTime; + if (DateTime.TryParse(content, CultureInfo.CurrentCulture, DateTimeStyles.AssumeLocal, out valueDateTime)) + { + value = valueDateTime; + return true; + } + else + { + value = null; + return false; + } + } + } + + private bool TryGetNullableBool(string content, out bool? value) + { + if (string.IsNullOrWhiteSpace(content)) + { + value = null; + return true; + } + else + { + content = content.Trim(); + + if (TrueValues.Contains(content, StringComparer.OrdinalIgnoreCase)) + { + value = true; + return true; + } + else if (FalseValues.Contains(content, StringComparer.OrdinalIgnoreCase)) + { + value = false; + return true; + } + else + { + value = null; + return false; + } + } + } + + private bool TryGetNullableInt(string content, out int? value) + { + if (string.IsNullOrWhiteSpace(content)) + { + value = null; + return true; + } + else + { + int intValue; + if (int.TryParse(content, out intValue)) + { + value = intValue; + return true; + } + else + { + value = null; + return false; + } + } + } + } +} \ No newline at end of file diff --git a/Disco.Services/Devices/Importing/DeviceImport.cs b/Disco.Services/Devices/Importing/DeviceImport.cs index d3394e08..e97ea2da 100644 --- a/Disco.Services/Devices/Importing/DeviceImport.cs +++ b/Disco.Services/Devices/Importing/DeviceImport.cs @@ -3,170 +3,143 @@ using Disco.Models.Repository; using Disco.Models.Services.Devices.Importing; using Disco.Services.Devices.Importing.Fields; using Disco.Services.Tasks; -using LumenWorks.Framework.IO.Csv; using System; using System.Collections.Generic; using System.Data; using System.IO; using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Disco.Services.Devices.Importing { public static class DeviceImport { - internal static Lazy> FieldHandlers = new Lazy>(() => - { - return new Dictionary() - { - { DeviceImportFieldTypes.DeviceSerialNumber, typeof(DeviceSerialNumberImportField) }, - { DeviceImportFieldTypes.DeviceAssetNumber, typeof(DeviceAssetNumberImportField) }, - { DeviceImportFieldTypes.DeviceLocation, typeof(DeviceLocationImportField) }, - { DeviceImportFieldTypes.DeviceAllowUnauthenticatedEnrol, typeof(DeviceAllowUnauthenticatedEnrolImportField) }, - { DeviceImportFieldTypes.DeviceDecommissionedDate, typeof(DeviceDecommissionedDateImportField) }, - { DeviceImportFieldTypes.DeviceDecommissionedReason, typeof(DeviceDecommissionedReasonImportField) }, - - { DeviceImportFieldTypes.DetailLanMacAddress, typeof(DetailLanMacAddressImportField) }, - { DeviceImportFieldTypes.DetailWLanMacAddress, typeof(DetailWLanMacAddressImportField) }, - { DeviceImportFieldTypes.DetailACAdapter, typeof(DetailACAdapterImportField) }, - { DeviceImportFieldTypes.DetailBattery, typeof(DetailBatteryImportField) }, - { DeviceImportFieldTypes.DetailKeyboard, typeof(DetailKeyboardImportField) }, - - { DeviceImportFieldTypes.ModelId, typeof(ModelIdImportField) }, - - { DeviceImportFieldTypes.BatchId, typeof(BatchIdImportField) }, - - { DeviceImportFieldTypes.ProfileId, typeof(ProfileIdImportField) }, - - { DeviceImportFieldTypes.AssignedUserId, typeof(AssignedUserIdImportField) } - }; - }); - - public static DeviceImportContext BeginImport(DiscoDataContext Database, string Filename, bool HasHeader, Stream FileContent) + public static IDeviceImportContext BeginImport(DiscoDataContext Database, string Filename, bool HasHeader, Stream FileContent) { if (FileContent == null) - throw new ArgumentNullException("FileContent"); + throw new ArgumentNullException(nameof(FileContent)); - if (string.IsNullOrWhiteSpace(Filename)) - Filename = ""; + if (FileContent.Length == 0) + throw new ArgumentNullException(nameof(FileContent)); - DeviceImportContext context; - List> header; - List rawData; + IDeviceImportContext context; - using (TextReader csvTextReader = new StreamReader(FileContent)) + if (Filename?.EndsWith(".xlsx", StringComparison.OrdinalIgnoreCase) ?? false) { - using (CsvReader csvReader = new CsvReader(csvTextReader, HasHeader)) - { - csvReader.DefaultParseErrorAction = ParseErrorAction.ThrowException; - csvReader.MissingFieldAction = MissingFieldAction.ReplaceByNull; - - rawData = csvReader.ToList(); - header = csvReader.GetFieldHeaders().Select(h => Tuple.Create(h, DeviceImportFieldTypes.IgnoreColumn)).ToList(); - } + // Use Xlsx Context + context = new XlsxDeviceImportContext(Filename, HasHeader, FileContent); + } + else + { + // Use/Default Csv Context + context = new CsvDeviceImportContext(Filename, HasHeader, FileContent); } - - context = new DeviceImportContext(Filename, header, rawData); context.GuessHeaderTypes(Database); return context; } - private static void GuessHeaderTypes(this DeviceImportContext Context, DiscoDataContext Database) + private static void GuessHeaderTypes(this IDeviceImportContext Context, DiscoDataContext Database) { - FieldHandlers.Value.ToList().ForEach(h => + using (var dataReader = Context.GetDataReader()) { - var instance = (DeviceImportFieldBase)Activator.CreateInstance(h.Value); - var column = instance.GuessHeader(Database, Context); - if (column.HasValue) - Context.Header[column.Value] = Tuple.Create(Context.Header[column.Value].Item1, instance.FieldType); - }); + foreach (var fieldHandler in Context.GetFieldHandlers()) + { + dataReader.Reset(); + + var instance = (DeviceImportFieldBase)Activator.CreateInstance(fieldHandler.Value); + var column = instance.GuessColumn(Database, Context, dataReader); + + if (column.HasValue) + Context.SetColumnType(column.Value, instance.FieldType); + } + } } - public static void UpdateHeaderTypes(this DeviceImportContext Context, List HeaderTypes) + public static void UpdateColumnTypes(this IDeviceImportContext Context, List ColumnTypes) { - if (HeaderTypes == null) - throw new ArgumentNullException("HeaderTypes"); + if (ColumnTypes == null) + throw new ArgumentNullException(nameof(ColumnTypes)); - if (HeaderTypes.Count != Context.Header.Count) - throw new ArgumentException("The number of Header Types supplied does not match the number of Headers", "HeaderTypes"); + if (ColumnTypes.Count != Context.ColumnCount) + throw new ArgumentException("The number of Column Types supplied does not match the number of Headers", nameof(ColumnTypes)); - if (!HeaderTypes.Any(h => h == DeviceImportFieldTypes.DeviceSerialNumber)) - throw new ArgumentException("At least one column must be the Device Serial Number", "HeaderTypes"); + if (!ColumnTypes.Any(h => h == DeviceImportFieldTypes.DeviceSerialNumber)) + throw new ArgumentException("At least one column must be the Device Serial Number", nameof(ColumnTypes)); - if (HeaderTypes.Where(h => h != DeviceImportFieldTypes.IgnoreColumn).GroupBy(h => h, (k, i) => Tuple.Create(k, i.Count())).Any(g => g.Item2 > 1)) - throw new ArgumentException("Column types can only be specified once for each type", "HeaderTypes"); + if (ColumnTypes.Where(h => h != DeviceImportFieldTypes.IgnoreColumn).GroupBy(h => h, (k, i) => Tuple.Create(k, i.Count())).Any(g => g.Item2 > 1)) + throw new ArgumentException("Column types can only be specified once for each type", nameof(ColumnTypes)); - Context.Header = Context.Header.Zip(HeaderTypes, (h, ht) => Tuple.Create(h.Item1, ht)).ToList(); + for (int columnIndex = 0; columnIndex < Context.ColumnCount; columnIndex++) + { + Context.SetColumnType(columnIndex, ColumnTypes[columnIndex]); + } } - public static void ParseRecords(this DeviceImportContext Context, DiscoDataContext Database, IScheduledTaskStatus Status) + public static void ParseRecords(this IDeviceImportContext Context, DiscoDataContext Database, IScheduledTaskStatus Status) { - if (Context.Header == null) - throw new InvalidOperationException("The Import Context has not been initialized"); + if (Context.ColumnCount == 0) + throw new InvalidOperationException("No columns were found"); - if (Context.Header.Count == 0) - throw new InvalidOperationException("No Headers were found"); + if (!Context.GetColumnByType(DeviceImportFieldTypes.DeviceSerialNumber).HasValue) + throw new ArgumentException("At least one column must be the Device Serial Number", nameof(Context.Columns)); - if (!Context.Header.Any(h => h.Item2 == DeviceImportFieldTypes.DeviceSerialNumber)) - throw new ArgumentException("At least one column must be the Device Serial Number", "Header"); - - if (Context.RawData == null || Context.RawData.Count == 0) - throw new ArgumentException("No data was found in the import file", "RawData"); + if (Context.RecordCount == 0) + throw new ArgumentException("No data was found in the import file", nameof(Context.RecordCount)); IDeviceImportCache cache; - if (Context.RawData.Count > 20) + if (Context.RecordCount > 20) cache = new DeviceImportInMemoryCache(Database); else cache = new DeviceImportDatabaseCache(Database); - Context.HeaderDeviceSerialNumberIndex = Context.Header.IndexOf(Context.Header.First(h => h.Item2 == DeviceImportFieldTypes.DeviceSerialNumber)); - Context.ParsedHeaders = Context.Header - .Select((h, i) => Tuple.Create(h.Item1, h.Item2, i)) - .Where(h => h.Item2 != DeviceImportFieldTypes.IgnoreColumn) - .Select(h => new Tuple, Type>(h.Item1, h.Item2, (f) => f[h.Item3], DeviceImport.FieldHandlers.Value[h.Item2])) + var deviceSerialNumberIndex = Context.GetColumnByType(DeviceImportFieldTypes.DeviceSerialNumber).Value; + var columns = Context.Columns + .Where(h => h.Type != DeviceImportFieldTypes.IgnoreColumn) .ToList(); Status.UpdateStatus(0, "Parsing Import Records", "Starting..."); - Context.Records = Context.RawData.Select((d, recordIndex) => + var records = new List(); + + using (var dataReader = Context.GetDataReader()) { - string deviceSerialNumber = Fields.DeviceSerialNumberImportField.ParseRawDeviceSerialNumber(d[Context.HeaderDeviceSerialNumberIndex]); - - Status.UpdateStatus(((double)recordIndex / Context.RawData.Count) * 100, string.Format("Parsing: {0}", deviceSerialNumber)); - - Device existingDevice = null; - if (Fields.DeviceSerialNumberImportField.IsDeviceSerialNumberValid(deviceSerialNumber)) - existingDevice = cache.Devices.FirstOrDefault(device => device.SerialNumber == deviceSerialNumber); - - var values = Context.ParsedHeaders - .ToDictionary(k => k.Item2, k => k.Item3(d)); - - var fields = Context.ParsedHeaders.Select(h => + while (dataReader.Read()) { - var f = (DeviceImportFieldBase)Activator.CreateInstance(h.Item4); - f.Parse(Database, cache, Context, recordIndex, deviceSerialNumber, existingDevice, values, h.Item3(d)); - return f; - }).ToList(); + string deviceSerialNumber = DeviceSerialNumberImportField.ParseRawDeviceSerialNumber(dataReader.GetString(deviceSerialNumberIndex)); - EntityState recordAction; - if (fields.Any(f => !f.FieldAction.HasValue)) - recordAction = EntityState.Detached; - else if (existingDevice == null) - recordAction = EntityState.Added; - else if (fields.Any(f => f.FieldAction == EntityState.Modified)) - recordAction = EntityState.Modified; - else - recordAction = EntityState.Unchanged; + Status.UpdateStatus(((double)dataReader.Index / Context.RecordCount) * 100, string.Format("Parsing: {0}", deviceSerialNumber)); - return new DeviceImportRecord(deviceSerialNumber, fields, recordAction); - }).Cast().ToList(); + Device existingDevice = null; + if (DeviceSerialNumberImportField.IsDeviceSerialNumberValid(deviceSerialNumber)) + existingDevice = cache.Devices.FirstOrDefault(device => device.SerialNumber == deviceSerialNumber); + + var fields = columns.Select(h => + { + var f = (DeviceImportFieldBase)h.GetHandlerInstance(); + f.Parse(Database, cache, Context, deviceSerialNumber, existingDevice, records, dataReader, h.Index); + return f; + }).ToList(); + + EntityState recordAction; + if (fields.Any(f => !f.FieldAction.HasValue)) + recordAction = EntityState.Detached; + else if (existingDevice == null) + recordAction = EntityState.Added; + else if (fields.Any(f => f.FieldAction == EntityState.Modified)) + recordAction = EntityState.Modified; + else + recordAction = EntityState.Unchanged; + + records.Add(new DeviceImportRecord(dataReader.Index, deviceSerialNumber, fields, recordAction)); + } + } + + Context.Records = records; } - public static int ApplyRecords(this DeviceImportContext Context, DiscoDataContext Database, IScheduledTaskStatus Status) + public static int ApplyRecords(this IDeviceImportContext Context, DiscoDataContext Database, IScheduledTaskStatus Status) { if (Context.Records == null) throw new InvalidOperationException("Import Records have not been parsed"); diff --git a/Disco.Services/Devices/Importing/DeviceImportContext.cs b/Disco.Services/Devices/Importing/DeviceImportContext.cs deleted file mode 100644 index baa5ff4c..00000000 --- a/Disco.Services/Devices/Importing/DeviceImportContext.cs +++ /dev/null @@ -1,35 +0,0 @@ -using Disco.Data.Repository; -using Disco.Models.Repository; -using Disco.Models.Services.Devices.Importing; -using Disco.Services.Tasks; -using System; -using System.Collections.Generic; -using System.Data; -using System.Linq; - -namespace Disco.Services.Devices.Importing -{ - public class DeviceImportContext : IDeviceImportContext - { - public string SessionId { get; private set; } - public string Filename { get; private set; } - - public List> Header { get; internal set; } - public List, Type>> ParsedHeaders { get; internal set; } - internal int HeaderDeviceSerialNumberIndex { get; set; } - - public List RawData { get; private set; } - - public List Records { get; internal set; } - public int AffectedRecords { get; internal set; } - - internal DeviceImportContext(string Filename, List> Header, List RawData) - { - this.SessionId = Guid.NewGuid().ToString("D"); - - this.Filename = Filename; - this.Header = Header; - this.RawData = RawData; - } - } -} \ No newline at end of file diff --git a/Disco.Services/Devices/Importing/DeviceImportFieldBase.cs b/Disco.Services/Devices/Importing/DeviceImportFieldBase.cs index 60ea57eb..95527180 100644 --- a/Disco.Services/Devices/Importing/DeviceImportFieldBase.cs +++ b/Disco.Services/Devices/Importing/DeviceImportFieldBase.cs @@ -1,12 +1,8 @@ using Disco.Data.Repository; using Disco.Models.Repository; using Disco.Models.Services.Devices.Importing; -using System; using System.Collections.Generic; using System.Data; -using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Disco.Services.Devices.Importing { @@ -22,21 +18,22 @@ namespace Disco.Services.Devices.Importing public abstract string FriendlyValue { get; } public abstract string FriendlyPreviousValue { get; } - public abstract bool Parse(DiscoDataContext Database, IDeviceImportCache Cache, DeviceImportContext Context, int RecordIndex, string DeviceSerialNumber, Device ExistingDevice, Dictionary Values, string Value); + public abstract bool Parse(DiscoDataContext Database, IDeviceImportCache Cache, IDeviceImportContext Context, string DeviceSerialNumber, Device ExistingDevice, List PreviousRecords, IDeviceImportDataReader DataReader, int ColumnIndex); public abstract bool Apply(DiscoDataContext Database, Device Device); + public virtual void Applied(DiscoDataContext Database, Device Device, ref bool DeviceADDescriptionSet) { return; } - public abstract int? GuessHeader(DiscoDataContext Database, DeviceImportContext Context); + public abstract int? GuessColumn(DiscoDataContext Database, IDeviceImportContext Context, IDeviceImportDataReader DataReader); #region Helpers protected bool Error(string Message) { - this.ErrorMessage = Message; - this.FieldAction = null; + ErrorMessage = Message; + FieldAction = null; return false; } protected bool Success(EntityState Action) { - this.FieldAction = Action; + FieldAction = Action; return true; } #endregion diff --git a/Disco.Services/Devices/Importing/DeviceImportParseTask.cs b/Disco.Services/Devices/Importing/DeviceImportParseTask.cs index 894845bf..33c45c5c 100644 --- a/Disco.Services/Devices/Importing/DeviceImportParseTask.cs +++ b/Disco.Services/Devices/Importing/DeviceImportParseTask.cs @@ -1,4 +1,5 @@ using Disco.Data.Repository; +using Disco.Models.Services.Devices.Importing; using Disco.Services.Tasks; using Quartz; using System; @@ -13,7 +14,7 @@ namespace Disco.Services.Devices.Importing public override bool SingleInstanceTask { get { return false; } } public override bool CancelInitiallySupported { get { return false; } } - public static ScheduledTaskStatus ScheduleNow(DeviceImportContext Context) + public static ScheduledTaskStatus ScheduleNow(IDeviceImportContext Context) { if (Context == null) throw new ArgumentNullException("Context"); @@ -28,11 +29,11 @@ namespace Disco.Services.Devices.Importing protected override void ExecuteTask() { - var context = (DeviceImportContext)this.ExecutionContext.JobDetail.JobDataMap[JobDataMapContext]; + var context = (IDeviceImportContext)ExecutionContext.JobDetail.JobDataMap[JobDataMapContext]; using (DiscoDataContext Database = new DiscoDataContext()) { - context.ParseRecords(Database, this.Status); + context.ParseRecords(Database, Status); } } } diff --git a/Disco.Services/Devices/Importing/DeviceImportRecord.cs b/Disco.Services/Devices/Importing/DeviceImportRecord.cs index 6a78215c..1b006725 100644 --- a/Disco.Services/Devices/Importing/DeviceImportRecord.cs +++ b/Disco.Services/Devices/Importing/DeviceImportRecord.cs @@ -5,13 +5,12 @@ using System; using System.Collections.Generic; using System.Data; using System.Linq; -using System.Text; -using System.Threading.Tasks; namespace Disco.Services.Devices.Importing { internal class DeviceImportRecord : IDeviceImportRecord { + public int Index { get; private set; } public string DeviceSerialNumber { get; private set; } public IEnumerable Fields { get; private set; } @@ -23,8 +22,9 @@ namespace Disco.Services.Devices.Importing get { return Fields.Any(f => !f.FieldAction.HasValue); } } - internal DeviceImportRecord(string DeviceSerialNumber, IEnumerable Fields, EntityState RecordAction) + internal DeviceImportRecord(int Index, string DeviceSerialNumber, IEnumerable Fields, EntityState RecordAction) { + this.Index = Index; this.DeviceSerialNumber = DeviceSerialNumber; this.Fields = Fields; this.RecordAction = RecordAction; @@ -43,7 +43,7 @@ namespace Disco.Services.Devices.Importing } else if (RecordAction == EntityState.Modified) { - device = Database.Devices.Find(this.DeviceSerialNumber); + device = Database.Devices.Find(DeviceSerialNumber); } else if (RecordAction == EntityState.Added) { @@ -82,6 +82,13 @@ namespace Disco.Services.Devices.Importing if (changesMade) Database.SaveChanges(); + bool adDescriptionSet = false; + + foreach (var field in Fields.Cast()) + { + field.Applied(Database, device, ref adDescriptionSet); + } + return changesMade; } diff --git a/Disco.Services/Devices/Importing/Fields/AssignedUserIdImportField.cs b/Disco.Services/Devices/Importing/Fields/AssignedUserIdImportField.cs index 20749abb..b7d25944 100644 --- a/Disco.Services/Devices/Importing/Fields/AssignedUserIdImportField.cs +++ b/Disco.Services/Devices/Importing/Fields/AssignedUserIdImportField.cs @@ -22,18 +22,18 @@ namespace Disco.Services.Devices.Importing.Fields public override string FriendlyValue { get { return friendlyValue; } } public override string FriendlyPreviousValue { get { return friendlyPreviousValue; } } - public override bool Parse(DiscoDataContext Database, IDeviceImportCache Cache, DeviceImportContext Context, int RecordIndex, string DeviceSerialNumber, Device ExistingDevice, Dictionary Values, string Value) + public override bool Parse(DiscoDataContext Database, IDeviceImportCache Cache, IDeviceImportContext Context, string DeviceSerialNumber, Device ExistingDevice, List PreviousRecords, IDeviceImportDataReader DataReader, int ColumnIndex) { - friendlyValue = Value; + var value = friendlyValue = DataReader.GetString(ColumnIndex); - if (string.IsNullOrWhiteSpace(Value)) + if (string.IsNullOrWhiteSpace(value)) { friendlyValue = null; parsedValue = null; } else { - parsedValue = Value.Trim(); + parsedValue = value.Trim(); parsedValue = ActiveDirectory.ParseDomainAccountId(parsedValue); @@ -48,25 +48,32 @@ namespace Disco.Services.Devices.Importing.Fields // Check User Exists // Try Database - User user = Database.Users.FirstOrDefault(u => u.UserId == parsedValue); - try + using (var database = new DiscoDataContext()) { - // Try Updating from AD - user = UserService.GetUser(parsedValue, Database); + User user = database.Users.FirstOrDefault(u => u.UserId == parsedValue); + try + { + // Try Updating from AD + user = UserService.GetUser(parsedValue, database); + } + catch (Exception ex) + { + if (user == null) + return Error(ex.Message); + } + parsedValue = user.UserId; + friendlyValue = $"{user.DisplayName} [{user.UserId}]"; } - catch (Exception ex) - { - if (user == null) - return Error(ex.Message); - } - parsedValue = user.UserId; - friendlyValue = string.Format("{0} [{1}]", user.DisplayName, user.UserId); // Check Decommissioned bool? importDecommissioning = null; - if (Values.ContainsKey(DeviceImportFieldTypes.DeviceDecommissionedDate) || Values.ContainsKey(DeviceImportFieldTypes.DeviceDecommissionedReason)) - importDecommissioning = Values.ContainsKey(DeviceImportFieldTypes.DeviceDecommissionedDate) && !string.IsNullOrWhiteSpace(Values[DeviceImportFieldTypes.DeviceDecommissionedDate]) || - Values.ContainsKey(DeviceImportFieldTypes.DeviceDecommissionedReason) && !string.IsNullOrWhiteSpace(Values[DeviceImportFieldTypes.DeviceDecommissionedReason]); + int? decommissionedDateIndex = Context.GetColumnByType(DeviceImportFieldTypes.DeviceDecommissionedDate); + int? decommissionedReasonIndex = Context.GetColumnByType(DeviceImportFieldTypes.DeviceDecommissionedReason); + if (decommissionedDateIndex.HasValue || decommissionedReasonIndex.HasValue) + { + importDecommissioning = (decommissionedDateIndex.HasValue && !string.IsNullOrWhiteSpace(DataReader.GetString(decommissionedDateIndex.Value))) || + (decommissionedReasonIndex.HasValue && !string.IsNullOrWhiteSpace(DataReader.GetString(decommissionedReasonIndex.Value))); + } if (importDecommissioning.HasValue && importDecommissioning.Value) return Error("Cannot assign a user to a device being decommissioned"); @@ -84,7 +91,7 @@ namespace Disco.Services.Devices.Importing.Fields else if (ExistingDevice != null && ExistingDevice.AssignedUserId != parsedValue) { if (ExistingDevice.AssignedUserId != null) - friendlyPreviousValue = string.Format("{0} [{1}]", ExistingDevice.AssignedUser.DisplayName, ExistingDevice.AssignedUser.UserId); + friendlyPreviousValue = $"{ExistingDevice.AssignedUser.DisplayName} [{ExistingDevice.AssignedUser.UserId}]"; else friendlyPreviousValue = null; @@ -96,7 +103,7 @@ namespace Disco.Services.Devices.Importing.Fields public override bool Apply(DiscoDataContext Database, Device Device) { - if (this.FieldAction == EntityState.Modified) + if (FieldAction == EntityState.Modified) { // Remove Current Assignments var currentAssignments = Device.DeviceUserAssignments.Where(dua => !dua.UnassignedDate.HasValue); @@ -130,16 +137,31 @@ namespace Disco.Services.Devices.Importing.Fields } } - public override int? GuessHeader(DiscoDataContext Database, DeviceImportContext Context) + public override void Applied(DiscoDataContext Database, Device Device, ref bool DeviceADDescriptionSet) + { + if (!DeviceADDescriptionSet) + { + if (ActiveDirectory.IsValidDomainAccountId(Device.DeviceDomainId)) + { + var adAccount = Device.ActiveDirectoryAccount(); + + if (adAccount != null && !adAccount.IsCriticalSystemObject) + { + adAccount.SetDescription(Device); + DeviceADDescriptionSet = true; + } + } + } + } + + public override int? GuessColumn(DiscoDataContext Database, IDeviceImportContext Context, IDeviceImportDataReader DataReader) { // column name - var possibleColumns = Context.Header - .Select((h, i) => Tuple.Create(h, i)) - .Where(h => h.Item1.Item2 == DeviceImportFieldTypes.IgnoreColumn && - h.Item1.Item1.IndexOf("user", System.StringComparison.OrdinalIgnoreCase) >= 0 - ); + var possibleColumns = Context.Columns + .Where(h => h.Type == DeviceImportFieldTypes.IgnoreColumn && + h.Name.IndexOf("user", StringComparison.OrdinalIgnoreCase) >= 0); - return possibleColumns.Select(h => (int?)h.Item2).FirstOrDefault(); + return possibleColumns.Select(h => (int?)h.Index).FirstOrDefault(); } } } diff --git a/Disco.Services/Devices/Importing/Fields/BatchIdImportField.cs b/Disco.Services/Devices/Importing/Fields/BatchIdImportField.cs index 797b46f8..5b9e9082 100644 --- a/Disco.Services/Devices/Importing/Fields/BatchIdImportField.cs +++ b/Disco.Services/Devices/Importing/Fields/BatchIdImportField.cs @@ -20,28 +20,23 @@ namespace Disco.Services.Devices.Importing.Fields public override string FriendlyValue { get { return friendlyValue; } } public override string FriendlyPreviousValue { get { return friendlyPreviousValue; } } - public override bool Parse(DiscoDataContext Database, IDeviceImportCache Cache, DeviceImportContext Context, int RecordIndex, string DeviceSerialNumber, Device ExistingDevice, Dictionary Values, string Value) + public override bool Parse(DiscoDataContext Database, IDeviceImportCache Cache, IDeviceImportContext Context, string DeviceSerialNumber, Device ExistingDevice, List PreviousRecords, IDeviceImportDataReader DataReader, int ColumnIndex) { - friendlyValue = Value; - - // Validate - if (string.IsNullOrWhiteSpace(Value)) - this.parsedValue = null; // Default = null + if (DataReader.TryGetNullableInt(ColumnIndex, out parsedValue)) + { + friendlyValue = parsedValue.ToString(); + } else { - int valueInt; - if (int.TryParse(Value, out valueInt)) - this.parsedValue = valueInt; - else - return Error("The Batch Identifier must be a number"); + return Error("The Batch Identifier must be a number"); } - if (this.parsedValue.HasValue) + if (parsedValue.HasValue) { var b = Cache.DeviceBatches.FirstOrDefault(db => db.Id == parsedValue); if (b == null) - return Error(string.Format("The identifier ({0}) does not match any Device Batch", Value)); - friendlyValue = string.Format("{0} [{1}]", b.Name, b.Id); + return Error($"The identifier ({friendlyValue}) does not match any Device Batch"); + friendlyValue = $"{b.Name} [{b.Id}]"; } else friendlyValue = null; @@ -55,7 +50,7 @@ namespace Disco.Services.Devices.Importing.Fields previousBatch = Cache.DeviceBatches.FirstOrDefault(db => db.Id == ExistingDevice.DeviceBatchId.Value); if (previousBatch != null) - friendlyPreviousValue = string.Format("{0} [{1}]", previousBatch.Name, previousBatch.Id); + friendlyPreviousValue = $"{previousBatch.Name} [{previousBatch.Id}]"; return Success(EntityState.Modified); } @@ -65,10 +60,10 @@ namespace Disco.Services.Devices.Importing.Fields public override bool Apply(DiscoDataContext Database, Device Device) { - if (this.FieldAction == EntityState.Added || - this.FieldAction == EntityState.Modified) + if (FieldAction == EntityState.Added || + FieldAction == EntityState.Modified) { - Device.DeviceBatchId = this.parsedValue; + Device.DeviceBatchId = parsedValue; return true; } else @@ -77,30 +72,27 @@ namespace Disco.Services.Devices.Importing.Fields } } - public override int? GuessHeader(DiscoDataContext Database, DeviceImportContext Context) + public override int? GuessColumn(DiscoDataContext Database, IDeviceImportContext Context, IDeviceImportDataReader DataReader) { // column name - var possibleColumns = Context.Header - .Select((h, i) => Tuple.Create(h, i)) - .Where(h => h.Item1.Item2 == DeviceImportFieldTypes.IgnoreColumn && h.Item1.Item1.IndexOf("batch", System.StringComparison.OrdinalIgnoreCase) >= 0); + var possibleColumns = Context.Columns + .Where(h => h.Type == DeviceImportFieldTypes.IgnoreColumn && + h.Name.IndexOf("batch", StringComparison.OrdinalIgnoreCase) >= 0); - // All Integers Numbers - possibleColumns = possibleColumns.Where(h => - { - int lastValue; - return Context.RawData.Select(v => v[h.Item2]).Take(100).Where(v => !string.IsNullOrWhiteSpace(v)).All(v => int.TryParse(v, out lastValue)); - }).ToList(); + // All Nullable Values + possibleColumns = possibleColumns + .Where(h => DataReader.TestAllNullableInt(h.Index)).ToList(); // Multiple Columns, tighten column definition if (possibleColumns.Count() > 1) { possibleColumns = possibleColumns .Where(h => - h.Item1.Item1.IndexOf("batchid", StringComparison.OrdinalIgnoreCase) >= 0 || - h.Item1.Item1.IndexOf("batch id", StringComparison.OrdinalIgnoreCase) >= 0); + h.Name.IndexOf("batchid", StringComparison.OrdinalIgnoreCase) >= 0 || + h.Name.IndexOf("batch id", StringComparison.OrdinalIgnoreCase) >= 0); } - return possibleColumns.Select(h => (int?)h.Item2).FirstOrDefault(); + return possibleColumns.Select(h => (int?)h.Index).FirstOrDefault(); } } } diff --git a/Disco.Services/Devices/Importing/Fields/DetailACAdapterImportField.cs b/Disco.Services/Devices/Importing/Fields/DetailACAdapterImportField.cs index d7e0b257..22c2275e 100644 --- a/Disco.Services/Devices/Importing/Fields/DetailACAdapterImportField.cs +++ b/Disco.Services/Devices/Importing/Fields/DetailACAdapterImportField.cs @@ -19,13 +19,15 @@ namespace Disco.Services.Devices.Importing.Fields public override string FriendlyValue { get { return parsedValue; } } public override string FriendlyPreviousValue { get { return previousValue; } } - public override bool Parse(DiscoDataContext Database, IDeviceImportCache Cache, DeviceImportContext Context, int RecordIndex, string DeviceSerialNumber, Device ExistingDevice, Dictionary Values, string Value) + public override bool Parse(DiscoDataContext Database, IDeviceImportCache Cache, IDeviceImportContext Context, string DeviceSerialNumber, Device ExistingDevice, List PreviousRecords, IDeviceImportDataReader DataReader, int ColumnIndex) { - if (string.IsNullOrWhiteSpace(Value)) + var value = DataReader.GetString(ColumnIndex); + + if (string.IsNullOrWhiteSpace(value)) parsedValue = null; else { - parsedValue = Value.Trim(); + parsedValue = value.Trim(); } if (ExistingDevice == null && parsedValue != null) @@ -54,8 +56,8 @@ namespace Disco.Services.Devices.Importing.Fields public override bool Apply(DiscoDataContext Database, Device Device) { - if (this.FieldAction == EntityState.Added || - this.FieldAction == EntityState.Modified) + if (FieldAction == EntityState.Added || + FieldAction == EntityState.Modified) { DeviceDetail detail = Database.DeviceDetails.FirstOrDefault(dd => @@ -84,16 +86,15 @@ namespace Disco.Services.Devices.Importing.Fields } } - public override int? GuessHeader(DiscoDataContext Database, DeviceImportContext Context) + public override int? GuessColumn(DiscoDataContext Database, IDeviceImportContext Context, IDeviceImportDataReader DataReader) { // column name - var possibleColumns = Context.Header - .Select((h, i) => Tuple.Create(h, i)) - .Where(h => h.Item1.Item2 == DeviceImportFieldTypes.IgnoreColumn && - (h.Item1.Item1.IndexOf("ac adapter", System.StringComparison.OrdinalIgnoreCase) >= 0 || - h.Item1.Item1.IndexOf("acadapter", System.StringComparison.OrdinalIgnoreCase) >= 0)); + var possibleColumns = Context.Columns + .Where(h => h.Type == DeviceImportFieldTypes.IgnoreColumn && + (h.Name.IndexOf("ac adapter", StringComparison.OrdinalIgnoreCase) >= 0 || + h.Name.IndexOf("acadapter", StringComparison.OrdinalIgnoreCase) >= 0)); - return possibleColumns.Select(h => (int?)h.Item2).FirstOrDefault(); + return possibleColumns.Select(h => (int?)h.Index).FirstOrDefault(); } } } diff --git a/Disco.Services/Devices/Importing/Fields/DetailBatteryImportField.cs b/Disco.Services/Devices/Importing/Fields/DetailBatteryImportField.cs index 373a6d15..e15c3824 100644 --- a/Disco.Services/Devices/Importing/Fields/DetailBatteryImportField.cs +++ b/Disco.Services/Devices/Importing/Fields/DetailBatteryImportField.cs @@ -19,13 +19,15 @@ namespace Disco.Services.Devices.Importing.Fields public override string FriendlyValue { get { return parsedValue; } } public override string FriendlyPreviousValue { get { return previousValue; } } - public override bool Parse(DiscoDataContext Database, IDeviceImportCache Cache, DeviceImportContext Context, int RecordIndex, string DeviceSerialNumber, Device ExistingDevice, Dictionary Values, string Value) + public override bool Parse(DiscoDataContext Database, IDeviceImportCache Cache, IDeviceImportContext Context, string DeviceSerialNumber, Device ExistingDevice, List PreviousRecords, IDeviceImportDataReader DataReader, int ColumnIndex) { - if (string.IsNullOrWhiteSpace(Value)) + var value = DataReader.GetString(ColumnIndex); + + if (string.IsNullOrWhiteSpace(value)) parsedValue = null; else { - parsedValue = Value.Trim(); + parsedValue = value.Trim(); } if (ExistingDevice == null && parsedValue != null) @@ -54,8 +56,8 @@ namespace Disco.Services.Devices.Importing.Fields public override bool Apply(DiscoDataContext Database, Device Device) { - if (this.FieldAction == EntityState.Added || - this.FieldAction == EntityState.Modified) + if (FieldAction == EntityState.Added || + FieldAction == EntityState.Modified) { DeviceDetail detail = Database.DeviceDetails.FirstOrDefault(dd => @@ -84,15 +86,14 @@ namespace Disco.Services.Devices.Importing.Fields } } - public override int? GuessHeader(DiscoDataContext Database, DeviceImportContext Context) + public override int? GuessColumn(DiscoDataContext Database, IDeviceImportContext Context, IDeviceImportDataReader DataReader) { // column name - var possibleColumns = Context.Header - .Select((h, i) => Tuple.Create(h, i)) - .Where(h => h.Item1.Item2 == DeviceImportFieldTypes.IgnoreColumn && - h.Item1.Item1.IndexOf("battery", System.StringComparison.OrdinalIgnoreCase) >= 0); + var possibleColumns = Context.Columns + .Where(h => h.Type == DeviceImportFieldTypes.IgnoreColumn && + h.Name.IndexOf("battery", StringComparison.OrdinalIgnoreCase) >= 0); - return possibleColumns.Select(h => (int?)h.Item2).FirstOrDefault(); + return possibleColumns.Select(h => (int?)h.Index).FirstOrDefault(); } } } diff --git a/Disco.Services/Devices/Importing/Fields/DetailKeyboardImportField.cs b/Disco.Services/Devices/Importing/Fields/DetailKeyboardImportField.cs index bf6524c3..7ea9c7ed 100644 --- a/Disco.Services/Devices/Importing/Fields/DetailKeyboardImportField.cs +++ b/Disco.Services/Devices/Importing/Fields/DetailKeyboardImportField.cs @@ -19,13 +19,15 @@ namespace Disco.Services.Devices.Importing.Fields public override string FriendlyValue { get { return parsedValue; } } public override string FriendlyPreviousValue { get { return previousValue; } } - public override bool Parse(DiscoDataContext Database, IDeviceImportCache Cache, DeviceImportContext Context, int RecordIndex, string DeviceSerialNumber, Device ExistingDevice, Dictionary Values, string Value) + public override bool Parse(DiscoDataContext Database, IDeviceImportCache Cache, IDeviceImportContext Context, string DeviceSerialNumber, Device ExistingDevice, List PreviousRecords, IDeviceImportDataReader DataReader, int ColumnIndex) { - if (string.IsNullOrWhiteSpace(Value)) + var value = DataReader.GetString(ColumnIndex); + + if (string.IsNullOrWhiteSpace(value)) parsedValue = null; else { - parsedValue = Value.Trim(); + parsedValue = value.Trim(); } if (ExistingDevice == null && parsedValue != null) @@ -54,8 +56,8 @@ namespace Disco.Services.Devices.Importing.Fields public override bool Apply(DiscoDataContext Database, Device Device) { - if (this.FieldAction == EntityState.Added || - this.FieldAction == EntityState.Modified) + if (FieldAction == EntityState.Added || + FieldAction == EntityState.Modified) { DeviceDetail detail = Database.DeviceDetails.FirstOrDefault(dd => @@ -84,15 +86,14 @@ namespace Disco.Services.Devices.Importing.Fields } } - public override int? GuessHeader(DiscoDataContext Database, DeviceImportContext Context) + public override int? GuessColumn(DiscoDataContext Database, IDeviceImportContext Context, IDeviceImportDataReader DataReader) { // column name - var possibleColumns = Context.Header - .Select((h, i) => Tuple.Create(h, i)) - .Where(h => h.Item1.Item2 == DeviceImportFieldTypes.IgnoreColumn && - h.Item1.Item1.IndexOf("keyboard", System.StringComparison.OrdinalIgnoreCase) >= 0); + var possibleColumns = Context.Columns + .Where(h => h.Type == DeviceImportFieldTypes.IgnoreColumn && + h.Name.IndexOf("keyboard", StringComparison.OrdinalIgnoreCase) >= 0); - return possibleColumns.Select(h => (int?)h.Item2).FirstOrDefault(); + return possibleColumns.Select(h => (int?)h.Index).FirstOrDefault(); } } } diff --git a/Disco.Services/Devices/Importing/Fields/DetailLanMacAddressImportField.cs b/Disco.Services/Devices/Importing/Fields/DetailLanMacAddressImportField.cs index 7db9ff68..e9a331f3 100644 --- a/Disco.Services/Devices/Importing/Fields/DetailLanMacAddressImportField.cs +++ b/Disco.Services/Devices/Importing/Fields/DetailLanMacAddressImportField.cs @@ -19,13 +19,15 @@ namespace Disco.Services.Devices.Importing.Fields public override string FriendlyValue { get { return parsedValue; } } public override string FriendlyPreviousValue { get { return previousValue; } } - public override bool Parse(DiscoDataContext Database, IDeviceImportCache Cache, DeviceImportContext Context, int RecordIndex, string DeviceSerialNumber, Device ExistingDevice, Dictionary Values, string Value) + public override bool Parse(DiscoDataContext Database, IDeviceImportCache Cache, IDeviceImportContext Context, string DeviceSerialNumber, Device ExistingDevice, List PreviousRecords, IDeviceImportDataReader DataReader, int ColumnIndex) { - if (string.IsNullOrWhiteSpace(Value)) + var value = DataReader.GetString(ColumnIndex); + + if (string.IsNullOrWhiteSpace(value)) parsedValue = null; else { - parsedValue = Value.Trim(); + parsedValue = value.Trim(); } if (ExistingDevice == null && parsedValue != null) @@ -54,8 +56,8 @@ namespace Disco.Services.Devices.Importing.Fields public override bool Apply(DiscoDataContext Database, Device Device) { - if (this.FieldAction == EntityState.Added || - this.FieldAction == EntityState.Modified) + if (FieldAction == EntityState.Added || + FieldAction == EntityState.Modified) { DeviceDetail detail = Database.DeviceDetails.FirstOrDefault(dd => @@ -84,23 +86,22 @@ namespace Disco.Services.Devices.Importing.Fields } } - public override int? GuessHeader(DiscoDataContext Database, DeviceImportContext Context) + public override int? GuessColumn(DiscoDataContext Database, IDeviceImportContext Context, IDeviceImportDataReader DataReader) { // column name - var possibleColumns = Context.Header - .Select((h, i) => Tuple.Create(h, i)) - .Where(h => h.Item1.Item2 == DeviceImportFieldTypes.IgnoreColumn && ( - h.Item1.Item1.IndexOf("wlan", System.StringComparison.OrdinalIgnoreCase) < 0 && - h.Item1.Item1.IndexOf("wireless", System.StringComparison.OrdinalIgnoreCase) < 0 && ( - h.Item1.Item1.IndexOf("lan address", System.StringComparison.OrdinalIgnoreCase) >= 0 || - h.Item1.Item1.IndexOf("lan mac", System.StringComparison.OrdinalIgnoreCase) >= 0 || - h.Item1.Item1.IndexOf("lan mac address", System.StringComparison.OrdinalIgnoreCase) >= 0 || - h.Item1.Item1.IndexOf("lanaddress", System.StringComparison.OrdinalIgnoreCase) >= 0 || - h.Item1.Item1.IndexOf("lanmac", System.StringComparison.OrdinalIgnoreCase) >= 0 || - h.Item1.Item1.IndexOf("lanmacaddress", System.StringComparison.OrdinalIgnoreCase) >= 0 + var possibleColumns = Context.Columns + .Where(h => h.Type == DeviceImportFieldTypes.IgnoreColumn && ( + h.Name.IndexOf("wlan", StringComparison.OrdinalIgnoreCase) < 0 && + h.Name.IndexOf("wireless", StringComparison.OrdinalIgnoreCase) < 0 && ( + h.Name.IndexOf("lan address", StringComparison.OrdinalIgnoreCase) >= 0 || + h.Name.IndexOf("lan mac", StringComparison.OrdinalIgnoreCase) >= 0 || + h.Name.IndexOf("lan mac address", StringComparison.OrdinalIgnoreCase) >= 0 || + h.Name.IndexOf("lanaddress", StringComparison.OrdinalIgnoreCase) >= 0 || + h.Name.IndexOf("lanmac", StringComparison.OrdinalIgnoreCase) >= 0 || + h.Name.IndexOf("lanmacaddress", StringComparison.OrdinalIgnoreCase) >= 0 ))); - return possibleColumns.Select(h => (int?)h.Item2).FirstOrDefault(); + return possibleColumns.Select(h => (int?)h.Index).FirstOrDefault(); } } } diff --git a/Disco.Services/Devices/Importing/Fields/DetailWLanMacAddressImportField.cs b/Disco.Services/Devices/Importing/Fields/DetailWLanMacAddressImportField.cs index 04a5411d..0da7adc3 100644 --- a/Disco.Services/Devices/Importing/Fields/DetailWLanMacAddressImportField.cs +++ b/Disco.Services/Devices/Importing/Fields/DetailWLanMacAddressImportField.cs @@ -19,13 +19,15 @@ namespace Disco.Services.Devices.Importing.Fields public override string FriendlyValue { get { return parsedValue; } } public override string FriendlyPreviousValue { get { return previousValue; } } - public override bool Parse(DiscoDataContext Database, IDeviceImportCache Cache, DeviceImportContext Context, int RecordIndex, string DeviceSerialNumber, Device ExistingDevice, Dictionary Values, string Value) + public override bool Parse(DiscoDataContext Database, IDeviceImportCache Cache, IDeviceImportContext Context, string DeviceSerialNumber, Device ExistingDevice, List PreviousRecords, IDeviceImportDataReader DataReader, int ColumnIndex) { - if (string.IsNullOrWhiteSpace(Value)) + var value = DataReader.GetString(ColumnIndex); + + if (string.IsNullOrWhiteSpace(value)) parsedValue = null; else { - parsedValue = Value.Trim(); + parsedValue = value.Trim(); } if (ExistingDevice == null && parsedValue != null) @@ -54,8 +56,8 @@ namespace Disco.Services.Devices.Importing.Fields public override bool Apply(DiscoDataContext Database, Device Device) { - if (this.FieldAction == EntityState.Added || - this.FieldAction == EntityState.Modified) + if (FieldAction == EntityState.Added || + FieldAction == EntityState.Modified) { DeviceDetail detail = Database.DeviceDetails.FirstOrDefault(dd => @@ -84,25 +86,24 @@ namespace Disco.Services.Devices.Importing.Fields } } - public override int? GuessHeader(DiscoDataContext Database, DeviceImportContext Context) + public override int? GuessColumn(DiscoDataContext Database, IDeviceImportContext Context, IDeviceImportDataReader DataReader) { // column name - var possibleColumns = Context.Header - .Select((h, i) => Tuple.Create(h, i)) - .Where(h => h.Item1.Item2 == DeviceImportFieldTypes.IgnoreColumn && ( - h.Item1.Item1.IndexOf("wireless lan mac address", System.StringComparison.OrdinalIgnoreCase) >= 0 || - h.Item1.Item1.IndexOf("wireless lan address", System.StringComparison.OrdinalIgnoreCase) >= 0 || - h.Item1.Item1.IndexOf("wireless mac address", System.StringComparison.OrdinalIgnoreCase) >= 0 || - h.Item1.Item1.IndexOf("wireless mac", System.StringComparison.OrdinalIgnoreCase) >= 0 || - h.Item1.Item1.IndexOf("wlan address", System.StringComparison.OrdinalIgnoreCase) >= 0 || - h.Item1.Item1.IndexOf("wlan mac", System.StringComparison.OrdinalIgnoreCase) >= 0 || - h.Item1.Item1.IndexOf("wlan mac address", System.StringComparison.OrdinalIgnoreCase) >= 0 || - h.Item1.Item1.IndexOf("wlanaddress", System.StringComparison.OrdinalIgnoreCase) >= 0 || - h.Item1.Item1.IndexOf("wlanmac", System.StringComparison.OrdinalIgnoreCase) >= 0 || - h.Item1.Item1.IndexOf("wlanmacaddress", System.StringComparison.OrdinalIgnoreCase) >= 0 + var possibleColumns = Context.Columns + .Where(h => h.Type == DeviceImportFieldTypes.IgnoreColumn && ( + h.Name.IndexOf("wireless lan mac address", StringComparison.OrdinalIgnoreCase) >= 0 || + h.Name.IndexOf("wireless lan address", StringComparison.OrdinalIgnoreCase) >= 0 || + h.Name.IndexOf("wireless mac address", StringComparison.OrdinalIgnoreCase) >= 0 || + h.Name.IndexOf("wireless mac", StringComparison.OrdinalIgnoreCase) >= 0 || + h.Name.IndexOf("wlan address", StringComparison.OrdinalIgnoreCase) >= 0 || + h.Name.IndexOf("wlan mac", StringComparison.OrdinalIgnoreCase) >= 0 || + h.Name.IndexOf("wlan mac address", StringComparison.OrdinalIgnoreCase) >= 0 || + h.Name.IndexOf("wlanaddress", StringComparison.OrdinalIgnoreCase) >= 0 || + h.Name.IndexOf("wlanmac", StringComparison.OrdinalIgnoreCase) >= 0 || + h.Name.IndexOf("wlanmacaddress", StringComparison.OrdinalIgnoreCase) >= 0 )); - return possibleColumns.Select(h => (int?)h.Item2).FirstOrDefault(); + return possibleColumns.Select(h => (int?)h.Index).FirstOrDefault(); } } } diff --git a/Disco.Services/Devices/Importing/Fields/DeviceAllowUnauthenticatedEnrolImportField.cs b/Disco.Services/Devices/Importing/Fields/DeviceAllowUnauthenticatedEnrolImportField.cs index 26a91836..7d4c59af 100644 --- a/Disco.Services/Devices/Importing/Fields/DeviceAllowUnauthenticatedEnrolImportField.cs +++ b/Disco.Services/Devices/Importing/Fields/DeviceAllowUnauthenticatedEnrolImportField.cs @@ -1,7 +1,6 @@ using Disco.Data.Repository; using Disco.Models.Repository; using Disco.Models.Services.Devices.Importing; -using Disco.Services.Users; using System; using System.Collections.Generic; using System.Data; @@ -11,9 +10,7 @@ namespace Disco.Services.Devices.Importing.Fields { internal class DeviceAllowUnauthenticatedEnrolImportField : DeviceImportFieldBase { - private static string[] TrueValues = { "true", "1", "yes", "-1", "on" }; - private static string[] FalseValues = { "false", "0", "no", "off" }; - private bool parsedValue; + private bool? parsedValue; private string friendlyValue; private string friendlyPreviousValue; @@ -23,22 +20,30 @@ namespace Disco.Services.Devices.Importing.Fields public override string FriendlyValue { get { return friendlyValue; } } public override string FriendlyPreviousValue { get { return friendlyPreviousValue; } } - public override bool Parse(DiscoDataContext Database, IDeviceImportCache Cache, DeviceImportContext Context, int RecordIndex, string DeviceSerialNumber, Device ExistingDevice, Dictionary Values, string Value) + public override bool Parse(DiscoDataContext Database, IDeviceImportCache Cache, IDeviceImportContext Context, string DeviceSerialNumber, Device ExistingDevice, List PreviousRecords, IDeviceImportDataReader DataReader, int ColumnIndex) { - friendlyValue = Value; - - if (!ParseBoolean(Value, out parsedValue)) + if (DataReader.TryGetNullableBool(ColumnIndex, out parsedValue)) + { + friendlyValue = parsedValue.ToString(); + } + else + { return Error("Expected a Boolean expression (True, 1, Yes, On, False, 0, No, Off)"); + } - friendlyValue = parsedValue.ToString(); + friendlyValue = parsedValue?.ToString() ?? "Not Set"; - if (parsedValue == true) + if (parsedValue.HasValue && parsedValue.Value == true) { // Check Decommissioned bool? importDecommissioning = null; - if (Values.ContainsKey(DeviceImportFieldTypes.DeviceDecommissionedDate) || Values.ContainsKey(DeviceImportFieldTypes.DeviceDecommissionedReason)) - importDecommissioning = Values.ContainsKey(DeviceImportFieldTypes.DeviceDecommissionedDate) && !string.IsNullOrWhiteSpace(Values[DeviceImportFieldTypes.DeviceDecommissionedDate]) || - Values.ContainsKey(DeviceImportFieldTypes.DeviceDecommissionedReason) && !string.IsNullOrWhiteSpace(Values[DeviceImportFieldTypes.DeviceDecommissionedReason]); + int? decommissionedDateIndex = Context.GetColumnByType(DeviceImportFieldTypes.DeviceDecommissionedDate); + int? decommissionedReasonIndex = Context.GetColumnByType(DeviceImportFieldTypes.DeviceDecommissionedReason); + if (decommissionedDateIndex.HasValue || decommissionedReasonIndex.HasValue) + { + importDecommissioning = (decommissionedDateIndex.HasValue && !string.IsNullOrWhiteSpace(DataReader.GetString(decommissionedDateIndex.Value))) || + (decommissionedReasonIndex.HasValue && !string.IsNullOrWhiteSpace(DataReader.GetString(decommissionedReasonIndex.Value))); + } if (importDecommissioning.HasValue && importDecommissioning.Value) return Error("Cannot enrol a device being decommissioned"); @@ -49,7 +54,11 @@ namespace Disco.Services.Devices.Importing.Fields } } - if (ExistingDevice == null && parsedValue != false) // Default: True + if (!parsedValue.HasValue) + { + return Success(EntityState.Unchanged); + } + else if (ExistingDevice == null && parsedValue.Value != false) // Default: True { return Success(EntityState.Added); } @@ -65,10 +74,11 @@ namespace Disco.Services.Devices.Importing.Fields public override bool Apply(DiscoDataContext Database, Device Device) { - if (this.FieldAction == EntityState.Modified || - this.FieldAction == EntityState.Added) + if (parsedValue.HasValue && + (FieldAction == EntityState.Modified || + FieldAction == EntityState.Added)) { - Device.AllowUnauthenticatedEnrol = parsedValue; + Device.AllowUnauthenticatedEnrol = parsedValue.Value; return true; } @@ -78,52 +88,20 @@ namespace Disco.Services.Devices.Importing.Fields } } - public override int? GuessHeader(DiscoDataContext Database, DeviceImportContext Context) + public override int? GuessColumn(DiscoDataContext Database, IDeviceImportContext Context, IDeviceImportDataReader DataReader) { // column name - var possibleColumns = Context.Header - .Select((h, i) => Tuple.Create(h, i)) - .Where(h => h.Item1.Item2 == DeviceImportFieldTypes.IgnoreColumn && - h.Item1.Item1.IndexOf("trust enrol", System.StringComparison.OrdinalIgnoreCase) >= 0 || - h.Item1.Item1.IndexOf("enrolment trusted", System.StringComparison.OrdinalIgnoreCase) >= 0 || - h.Item1.Item1.IndexOf("trust", System.StringComparison.OrdinalIgnoreCase) >= 0 + var possibleColumns = Context.Columns + .Where(h => h.Type == DeviceImportFieldTypes.IgnoreColumn && + h.Name.IndexOf("trust enrol", StringComparison.OrdinalIgnoreCase) >= 0 || + h.Name.IndexOf("enrolment trusted", StringComparison.OrdinalIgnoreCase) >= 0 || + h.Name.IndexOf("trust", StringComparison.OrdinalIgnoreCase) >= 0 ); // All Boolean - possibleColumns = possibleColumns.Where(h => - { - bool lastValue; - return Context.RawData.Select(v => v[h.Item2]).Take(100).Where(v => !string.IsNullOrWhiteSpace(v)).All(v => ParseBoolean(v, out lastValue)); - }).ToList(); + possibleColumns = possibleColumns.Where(h => DataReader.TestAllNullableBool(h.Index)).ToList(); - return possibleColumns.Select(h => (int?)h.Item2).FirstOrDefault(); - } - - private static bool ParseBoolean(string value, out bool result) - { - if (string.IsNullOrWhiteSpace(value)) - { - result = false; - return false; - } - - value = value.Trim(); - - if (TrueValues.Contains(value, StringComparer.OrdinalIgnoreCase)) - { - result = true; - return true; - } - else if (FalseValues.Contains(value, StringComparer.OrdinalIgnoreCase)) - { - result = false; - return true; - } - else - { - result = false; - return false; - } + return possibleColumns.Select(h => (int?)h.Index).FirstOrDefault(); } } } diff --git a/Disco.Services/Devices/Importing/Fields/DeviceAssetNumberImportField.cs b/Disco.Services/Devices/Importing/Fields/DeviceAssetNumberImportField.cs index f52c9674..7af643b5 100644 --- a/Disco.Services/Devices/Importing/Fields/DeviceAssetNumberImportField.cs +++ b/Disco.Services/Devices/Importing/Fields/DeviceAssetNumberImportField.cs @@ -19,13 +19,15 @@ namespace Disco.Services.Devices.Importing.Fields public override string FriendlyValue { get { return parsedValue; } } public override string FriendlyPreviousValue { get { return previousValue; } } - public override bool Parse(DiscoDataContext Database, IDeviceImportCache Cache, DeviceImportContext Context, int RecordIndex, string DeviceSerialNumber, Device ExistingDevice, Dictionary Values, string Value) + public override bool Parse(DiscoDataContext Database, IDeviceImportCache Cache, IDeviceImportContext Context, string DeviceSerialNumber, Device ExistingDevice, List PreviousRecords, IDeviceImportDataReader DataReader, int ColumnIndex) { - if (string.IsNullOrWhiteSpace(Value)) + var value = DataReader.GetString(ColumnIndex); + + if (string.IsNullOrWhiteSpace(value)) parsedValue = null; else { - parsedValue = Value.Trim(); + parsedValue = value.Trim(); if (parsedValue.Length > 40) return Error("Cannot be more than 40 characters"); } @@ -43,10 +45,10 @@ namespace Disco.Services.Devices.Importing.Fields public override bool Apply(DiscoDataContext Database, Device Device) { - if (this.FieldAction == EntityState.Added || - this.FieldAction == EntityState.Modified) + if (FieldAction == EntityState.Added || + FieldAction == EntityState.Modified) { - Device.AssetNumber = this.parsedValue; + Device.AssetNumber = parsedValue; return true; } else @@ -55,14 +57,14 @@ namespace Disco.Services.Devices.Importing.Fields } } - public override int? GuessHeader(DiscoDataContext Database, DeviceImportContext Context) + public override int? GuessColumn(DiscoDataContext Database, IDeviceImportContext Context, IDeviceImportDataReader DataReader) { - // 'asset' in column name - var possibleColumns = Context.Header - .Select((h, i) => Tuple.Create(h, i)) - .Where(h => h.Item1.Item2 == DeviceImportFieldTypes.IgnoreColumn && h.Item1.Item1.IndexOf("asset", System.StringComparison.OrdinalIgnoreCase) >= 0); + // column name + var possibleColumns = Context.Columns + .Where(h => h.Type == DeviceImportFieldTypes.IgnoreColumn && + h.Name.IndexOf("asset", StringComparison.OrdinalIgnoreCase) >= 0); - return possibleColumns.Select(h => (int?)h.Item2).FirstOrDefault(); + return possibleColumns.Select(h => (int?)h.Index).FirstOrDefault(); } } } diff --git a/Disco.Services/Devices/Importing/Fields/DeviceDecommissionedDateImportField.cs b/Disco.Services/Devices/Importing/Fields/DeviceDecommissionedDateImportField.cs index c9c48dd9..0e722d27 100644 --- a/Disco.Services/Devices/Importing/Fields/DeviceDecommissionedDateImportField.cs +++ b/Disco.Services/Devices/Importing/Fields/DeviceDecommissionedDateImportField.cs @@ -1,6 +1,7 @@ using Disco.Data.Repository; using Disco.Models.Repository; using Disco.Models.Services.Devices.Importing; +using Disco.Services.Interop.ActiveDirectory; using System; using System.Collections.Generic; using System.Data; @@ -24,53 +25,67 @@ namespace Disco.Services.Devices.Importing.Fields public override string FriendlyValue { get { return parsedValue.HasValue ? parsedValue.Value.ToString(DateFormat) : rawValue; } } public override string FriendlyPreviousValue { get { return previousValue.HasValue ? previousValue.Value.ToString(DateFormat) : null; } } - public override bool Parse(DiscoDataContext Database, IDeviceImportCache Cache, DeviceImportContext Context, int RecordIndex, string DeviceSerialNumber, Device ExistingDevice, Dictionary Values, string Value) + public override bool Parse(DiscoDataContext Database, IDeviceImportCache Cache, IDeviceImportContext Context, string DeviceSerialNumber, Device ExistingDevice, List PreviousRecords, IDeviceImportDataReader DataReader, int ColumnIndex) { - if (string.IsNullOrWhiteSpace(Value)) + if (!DataReader.TryGetNullableDateTime(ColumnIndex, out parsedValue)) { - rawValue = null; - parsedValue = null; + rawValue = DataReader.GetString(ColumnIndex); + return Error($"Cannot parse the value as a Date/Time using {CultureInfo.CurrentCulture.Name} culture (system default)."); } - else + + if (parsedValue.HasValue) { - DateTime valueDateTime; - if (!DateTime.TryParse(Value.Trim(), CultureInfo.CurrentCulture, System.Globalization.DateTimeStyles.AssumeLocal, out valueDateTime)) - { - rawValue = Value.Trim(); - return Error(string.Format("Cannot parse the value as a Date/Time using {0} culture (system default).", CultureInfo.CurrentCulture.Name)); - } - else - { - // Accuracy to the second (remove any milliseconds) - parsedValue = new DateTime((valueDateTime.Ticks / 10000000L) * 10000000L); - } + // Accuracy to the second (remove any milliseconds) + parsedValue = new DateTime((parsedValue.Value.Ticks / 10000000L) * 10000000L); } string errorMessage; - if (parsedValue.HasValue && !CanDecommissionDevice(ExistingDevice, Values, out errorMessage)) + if (parsedValue.HasValue && !CanDecommissionDevice(ExistingDevice, Context, DataReader, out errorMessage)) return Error(errorMessage); - setReason = !Values.ContainsKey(DeviceImportFieldTypes.DeviceDecommissionedReason) || - (parsedValue.HasValue && string.IsNullOrWhiteSpace(Values[DeviceImportFieldTypes.DeviceDecommissionedReason])); + var decommissionReasonIndex = Context.GetColumnByType(DeviceImportFieldTypes.DeviceDecommissionedReason); + setReason = !decommissionReasonIndex.HasValue || + (parsedValue.HasValue && string.IsNullOrWhiteSpace(DataReader.GetString(decommissionReasonIndex.Value))); - if (ExistingDevice != null && ExistingDevice.DecommissionedDate != parsedValue) + if (ExistingDevice != null && ExistingDevice.DecommissionedDate.HasValue) + { + // Accuracy to the second (remove any milliseconds) + previousValue = new DateTime((ExistingDevice.DecommissionedDate.Value.Ticks / 10000000L) * 10000000L); + } + else + { + previousValue = null; + } + + if (previousValue != parsedValue) { - previousValue = ExistingDevice.DecommissionedDate; return Success(EntityState.Modified); } else + { + previousValue = null; return Success(EntityState.Unchanged); + } } public override bool Apply(DiscoDataContext Database, Device Device) { - if (this.FieldAction == EntityState.Modified) + if (FieldAction == EntityState.Modified) { // Decommission or Recommission Device - Device.DecommissionedDate = this.parsedValue; + Device.DecommissionedDate = parsedValue; if (setReason) - Device.DecommissionReason = this.parsedValue.HasValue ? (DecommissionReasons?)DecommissionReasons.EndOfLife : null; + { + if (parsedValue.HasValue && !Device.DecommissionReason.HasValue) + { + Device.DecommissionReason = DecommissionReasons.EndOfLife; + } + else if (!parsedValue.HasValue && Device.DecommissionReason.HasValue) + { + Device.DecommissionReason = null; + } + } return true; } @@ -80,22 +95,52 @@ namespace Disco.Services.Devices.Importing.Fields } } - public override int? GuessHeader(DiscoDataContext Database, DeviceImportContext Context) + public override void Applied(DiscoDataContext Database, Device Device, ref bool DeviceADDescriptionSet) { - // column name - var possibleColumns = Context.Header - .Select((h, i) => Tuple.Create(h, i)) - .Where(h => h.Item1.Item2 == DeviceImportFieldTypes.IgnoreColumn && - h.Item1.Item1.IndexOf("decommission date", System.StringComparison.OrdinalIgnoreCase) >= 0 || - h.Item1.Item1.IndexOf("decommissiondate", System.StringComparison.OrdinalIgnoreCase) >= 0 || - h.Item1.Item1.IndexOf("decommissioned date", System.StringComparison.OrdinalIgnoreCase) >= 0 || - h.Item1.Item1.IndexOf("decommissioneddate", System.StringComparison.OrdinalIgnoreCase) >= 0 - ); + if (ActiveDirectory.IsValidDomainAccountId(Device.DeviceDomainId)) + { + var adAccount = Device.ActiveDirectoryAccount(); - return possibleColumns.Select(h => (int?)h.Item2).FirstOrDefault(); + if (adAccount != null && !adAccount.IsCriticalSystemObject) + { + if (Device.DecommissionedDate.HasValue) + { + // Disable AD Account + adAccount.DisableAccount(); + } + else + { + // Enable AD Account + adAccount.EnableAccount(); + } + + if (!DeviceADDescriptionSet) + { + adAccount.SetDescription(Device); + DeviceADDescriptionSet = true; + } + } + } } - public static bool CanDecommissionDevice(Device Device, Dictionary Values, out string ErrorMessage) + public override int? GuessColumn(DiscoDataContext Database, IDeviceImportContext Context, IDeviceImportDataReader DataReader) + { + // column name + var possibleColumns = Context.Columns + .Where(h => h.Type == DeviceImportFieldTypes.IgnoreColumn && + h.Name.IndexOf("decommission date", StringComparison.OrdinalIgnoreCase) >= 0 || + h.Name.IndexOf("decommissiondate", StringComparison.OrdinalIgnoreCase) >= 0 || + h.Name.IndexOf("decommissioned date", StringComparison.OrdinalIgnoreCase) >= 0 || + h.Name.IndexOf("decommissioneddate", StringComparison.OrdinalIgnoreCase) >= 0 + ); + + possibleColumns = possibleColumns + .Where(h => DataReader.TestAllNullableDateTime(h.Index)).ToList(); + + return possibleColumns.Select(h => (int?)h.Index).FirstOrDefault(); + } + + public static bool CanDecommissionDevice(Device Device, IDeviceImportContext Context, IDeviceImportDataReader DataReader, out string ErrorMessage) { if (Device == null) { @@ -105,13 +150,14 @@ namespace Disco.Services.Devices.Importing.Fields // Check device is assigned (or being removed in this import) - if ((!Values.ContainsKey(DeviceImportFieldTypes.AssignedUserId) && Device.AssignedUserId != null) || - (Values.ContainsKey(DeviceImportFieldTypes.AssignedUserId) && !string.IsNullOrWhiteSpace(Values[DeviceImportFieldTypes.AssignedUserId]))) + var assignedUserIndex = Context.GetColumnByType(DeviceImportFieldTypes.AssignedUserId); + if ((!assignedUserIndex.HasValue && Device.AssignedUserId != null) || + (assignedUserIndex.HasValue && !string.IsNullOrWhiteSpace(DataReader.GetString(assignedUserIndex.Value)))) { if (Device.AssignedUserId != null) - ErrorMessage = string.Format("The device is assigned to a user ({0} [{1}]) and cannot be decommissioned", Device.AssignedUser.DisplayName, Device.AssignedUser.UserId); + ErrorMessage = $"The device is assigned to a user ({Device.AssignedUser.DisplayName} [{Device.AssignedUser.UserId}]) and cannot be decommissioned"; else - ErrorMessage = string.Format("The device is being assigned to a user ({0}) and cannot be decommissioned", Values[DeviceImportFieldTypes.AssignedUserId]); + ErrorMessage = $"The device is being assigned to a user ({DataReader.GetString(assignedUserIndex.Value)}) and cannot be decommissioned"; return false; } @@ -119,7 +165,7 @@ namespace Disco.Services.Devices.Importing.Fields var openJobCount = Device.Jobs.Count(j => !j.ClosedDate.HasValue); if (openJobCount > 0) { - ErrorMessage = string.Format("The device is associated with {0} open job{1} and cannot be decommissioned", openJobCount, openJobCount == 1 ? null : "s"); + ErrorMessage = $"The device is associated with {openJobCount} open job{(openJobCount == 1 ? null : "s")} and cannot be decommissioned"; return false; } diff --git a/Disco.Services/Devices/Importing/Fields/DeviceDecommissionedReasonImportField.cs b/Disco.Services/Devices/Importing/Fields/DeviceDecommissionedReasonImportField.cs index 44b9c6e0..07d549c6 100644 --- a/Disco.Services/Devices/Importing/Fields/DeviceDecommissionedReasonImportField.cs +++ b/Disco.Services/Devices/Importing/Fields/DeviceDecommissionedReasonImportField.cs @@ -1,6 +1,7 @@ using Disco.Data.Repository; using Disco.Models.Repository; using Disco.Models.Services.Devices.Importing; +using Disco.Services.Interop.ActiveDirectory; using System; using System.Collections.Generic; using System.Data; @@ -22,9 +23,11 @@ namespace Disco.Services.Devices.Importing.Fields public override string FriendlyValue { get { return parsedValue.HasValue ? parsedValue.Value.ToString() : rawValue; } } public override string FriendlyPreviousValue { get { return previousValue.HasValue ? previousValue.Value.ToString() : null; } } - public override bool Parse(DiscoDataContext Database, IDeviceImportCache Cache, DeviceImportContext Context, int RecordIndex, string DeviceSerialNumber, Device ExistingDevice, Dictionary Values, string Value) + public override bool Parse(DiscoDataContext Database, IDeviceImportCache Cache, IDeviceImportContext Context, string DeviceSerialNumber, Device ExistingDevice, List PreviousRecords, IDeviceImportDataReader DataReader, int ColumnIndex) { - if (string.IsNullOrWhiteSpace(Value)) + var value = DataReader.GetString(ColumnIndex); + + if (string.IsNullOrWhiteSpace(value)) { rawValue = null; parsedValue = null; @@ -32,9 +35,9 @@ namespace Disco.Services.Devices.Importing.Fields else { DecommissionReasons valueReason; - if (!decommissionReasonsMap.Value.TryGetValue(Value.Trim(), out valueReason)) + if (!decommissionReasonsMap.Value.TryGetValue(value.Trim(), out valueReason)) { - rawValue = Value.Trim(); + rawValue = value.Trim(); return Error("Cannot parse the value as a Decommission Reason"); } else @@ -43,15 +46,16 @@ namespace Disco.Services.Devices.Importing.Fields } } - if (parsedValue.HasValue && !Values.ContainsKey(DeviceImportFieldTypes.DeviceDecommissionedDate)) + var decommissionedDateIndex = Context.GetColumnByType(DeviceImportFieldTypes.DeviceDecommissionedDate); + if (parsedValue.HasValue && !decommissionedDateIndex.HasValue) { string errorMessage; - if (!DeviceDecommissionedDateImportField.CanDecommissionDevice(ExistingDevice, Values, out errorMessage)) + if (!DeviceDecommissionedDateImportField.CanDecommissionDevice(ExistingDevice, Context, DataReader, out errorMessage)) return Error(errorMessage); setDate = true; } - else if (parsedValue.HasValue && string.IsNullOrWhiteSpace(Values[DeviceImportFieldTypes.DeviceDecommissionedDate])) + else if (parsedValue.HasValue && string.IsNullOrWhiteSpace(DataReader.GetString(decommissionedDateIndex.Value))) { setDate = true; } @@ -67,13 +71,22 @@ namespace Disco.Services.Devices.Importing.Fields public override bool Apply(DiscoDataContext Database, Device Device) { - if (this.FieldAction == EntityState.Modified) + if (FieldAction == EntityState.Modified) { // Decommission or Recommission Device - Device.DecommissionReason = this.parsedValue; + Device.DecommissionReason = parsedValue; if (setDate) - Device.DecommissionedDate = this.parsedValue.HasValue ? (DateTime?)DateTime.Now : null; + { + if (parsedValue.HasValue && !Device.DecommissionedDate.HasValue) + { + Device.DecommissionedDate = DateTime.Now; + } + else if (!parsedValue.HasValue && Device.DecommissionedDate.HasValue) + { + Device.DecommissionedDate = null; + } + } return true; } @@ -83,19 +96,50 @@ namespace Disco.Services.Devices.Importing.Fields } } - public override int? GuessHeader(DiscoDataContext Database, DeviceImportContext Context) + public override void Applied(DiscoDataContext Database, Device Device, ref bool DeviceADDescriptionSet) + { + // Only Enable/Disable if DeviceDecommissionedDate field is not present + if (setDate) + { + if (ActiveDirectory.IsValidDomainAccountId(Device.DeviceDomainId)) + { + var adAccount = Device.ActiveDirectoryAccount(); + + if (adAccount != null && !adAccount.IsCriticalSystemObject) + { + if (Device.DecommissionedDate.HasValue) + { + // Disable AD Account + adAccount.DisableAccount(); + } + else + { + // Enable AD Account + adAccount.EnableAccount(); + } + + if (!DeviceADDescriptionSet) + { + adAccount.SetDescription(Device); + DeviceADDescriptionSet = true; + } + } + } + } + } + + public override int? GuessColumn(DiscoDataContext Database, IDeviceImportContext Context, IDeviceImportDataReader DataReader) { // column name - var possibleColumns = Context.Header - .Select((h, i) => Tuple.Create(h, i)) - .Where(h => h.Item1.Item2 == DeviceImportFieldTypes.IgnoreColumn && - h.Item1.Item1.IndexOf("decommission reason", System.StringComparison.OrdinalIgnoreCase) >= 0 || - h.Item1.Item1.IndexOf("decommissionreason", System.StringComparison.OrdinalIgnoreCase) >= 0 || - h.Item1.Item1.IndexOf("decommissioned reason", System.StringComparison.OrdinalIgnoreCase) >= 0 || - h.Item1.Item1.IndexOf("decommissionedreason", System.StringComparison.OrdinalIgnoreCase) >= 0 + var possibleColumns = Context.Columns + .Where(h => h.Type == DeviceImportFieldTypes.IgnoreColumn && + h.Name.IndexOf("decommission reason", StringComparison.OrdinalIgnoreCase) >= 0 || + h.Name.IndexOf("decommissionreason", StringComparison.OrdinalIgnoreCase) >= 0 || + h.Name.IndexOf("decommissioned reason", StringComparison.OrdinalIgnoreCase) >= 0 || + h.Name.IndexOf("decommissionedreason", StringComparison.OrdinalIgnoreCase) >= 0 ); - return possibleColumns.Select(h => (int?)h.Item2).FirstOrDefault(); + return possibleColumns.Select(h => (int?)h.Index).FirstOrDefault(); } public static Dictionary BuildDecommissionReasonsMap() diff --git a/Disco.Services/Devices/Importing/Fields/DeviceLocationImportField.cs b/Disco.Services/Devices/Importing/Fields/DeviceLocationImportField.cs index 8e0eac8c..9851000f 100644 --- a/Disco.Services/Devices/Importing/Fields/DeviceLocationImportField.cs +++ b/Disco.Services/Devices/Importing/Fields/DeviceLocationImportField.cs @@ -19,13 +19,15 @@ namespace Disco.Services.Devices.Importing.Fields public override string FriendlyValue { get { return parsedValue; } } public override string FriendlyPreviousValue { get { return previousValue; } } - public override bool Parse(DiscoDataContext Database, IDeviceImportCache Cache, DeviceImportContext Context, int RecordIndex, string DeviceSerialNumber, Device ExistingDevice, Dictionary Values, string Value) + public override bool Parse(DiscoDataContext Database, IDeviceImportCache Cache, IDeviceImportContext Context, string DeviceSerialNumber, Device ExistingDevice, List PreviousRecords, IDeviceImportDataReader DataReader, int ColumnIndex) { - if (string.IsNullOrWhiteSpace(Value)) + var value = DataReader.GetString(ColumnIndex); + + if (string.IsNullOrWhiteSpace(value)) parsedValue = null; else { - parsedValue = Value.Trim(); + parsedValue = value.Trim(); if (parsedValue.Length > 250) return Error("Cannot be more than 250 characters"); } @@ -43,10 +45,10 @@ namespace Disco.Services.Devices.Importing.Fields public override bool Apply(DiscoDataContext Database, Device Device) { - if (this.FieldAction == EntityState.Added || - this.FieldAction == EntityState.Modified) + if (FieldAction == EntityState.Added || + FieldAction == EntityState.Modified) { - Device.Location = this.parsedValue; + Device.Location = parsedValue; return true; } else @@ -55,14 +57,14 @@ namespace Disco.Services.Devices.Importing.Fields } } - public override int? GuessHeader(DiscoDataContext Database, DeviceImportContext Context) + public override int? GuessColumn(DiscoDataContext Database, IDeviceImportContext Context, IDeviceImportDataReader DataReader) { // column name - var possibleColumns = Context.Header - .Select((h, i) => Tuple.Create(h, i)) - .Where(h => h.Item1.Item2 == DeviceImportFieldTypes.IgnoreColumn && h.Item1.Item1.IndexOf("location", System.StringComparison.OrdinalIgnoreCase) >= 0); + var possibleColumns = Context.Columns + .Where(h => h.Type == DeviceImportFieldTypes.IgnoreColumn && + h.Name.IndexOf("location", StringComparison.OrdinalIgnoreCase) >= 0); - return possibleColumns.Select(h => (int?)h.Item2).FirstOrDefault(); + return possibleColumns.Select(h => (int?)h.Index).FirstOrDefault(); } } } diff --git a/Disco.Services/Devices/Importing/Fields/DeviceSerialNumberImportField.cs b/Disco.Services/Devices/Importing/Fields/DeviceSerialNumberImportField.cs index a8678f5f..97cce89e 100644 --- a/Disco.Services/Devices/Importing/Fields/DeviceSerialNumberImportField.cs +++ b/Disco.Services/Devices/Importing/Fields/DeviceSerialNumberImportField.cs @@ -19,14 +19,16 @@ namespace Disco.Services.Devices.Importing.Fields public override string FriendlyValue { get { return parsedValue; } } public override string FriendlyPreviousValue { get { return parsedValue; } } - public override bool Parse(DiscoDataContext Database, IDeviceImportCache Cache, DeviceImportContext Context, int RecordIndex, string DeviceSerialNumber, Device ExistingDevice, Dictionary Values, string Value) + public override bool Parse(DiscoDataContext Database, IDeviceImportCache Cache, IDeviceImportContext Context, string DeviceSerialNumber, Device ExistingDevice, List PreviousRecords, IDeviceImportDataReader DataReader, int ColumnIndex) { + var value = DataReader.GetString(ColumnIndex); + // Validate - if (string.IsNullOrWhiteSpace(Value)) + if (string.IsNullOrWhiteSpace(value)) return Error("The Device Serial Number is required"); else { - parsedValue = Value.Trim(); + parsedValue = value.Trim(); if (parsedValue.Length > maxLength) return Error($"Cannot be more than {maxLength} characters"); if (parsedValue.Contains(@"/")) @@ -36,13 +38,11 @@ namespace Disco.Services.Devices.Importing.Fields } // Duplicate - var duplicate = Context.RawData - .Take(RecordIndex) - .Select((r, i) => Tuple.Create(i, ParseRawDeviceSerialNumber(r[Context.HeaderDeviceSerialNumberIndex]))) - .Where(r => IsDeviceSerialNumberValid(r.Item2)) - .FirstOrDefault(r => r.Item2.Equals(parsedValue, StringComparison.OrdinalIgnoreCase)); + var duplicate = PreviousRecords + .Where(r => IsDeviceSerialNumberValid(r.DeviceSerialNumber)) + .FirstOrDefault(r => r.DeviceSerialNumber.Equals(parsedValue, StringComparison.OrdinalIgnoreCase)); if (duplicate != null) - return Error($"This Device Serial Number was already present on Row {duplicate.Item1 + 1}"); + return Error($"This Device Serial Number was already present on Row {DataReader.GetRowNumber(duplicate.Index)}"); // No action required return Success(EntityState.Unchanged); @@ -54,18 +54,14 @@ namespace Disco.Services.Devices.Importing.Fields return false; } - public override int? GuessHeader(DiscoDataContext Database, DeviceImportContext Context) + public override int? GuessColumn(DiscoDataContext Database, IDeviceImportContext Context, IDeviceImportDataReader DataReader) { // 'serial' in column name - var possibleColumns = Context.Header - .Select((h, i) => Tuple.Create(h, i)) - .Where(h => h.Item1.Item2 == DeviceImportFieldTypes.IgnoreColumn && h.Item1.Item1.IndexOf("serial", System.StringComparison.OrdinalIgnoreCase) >= 0); + var possibleColumns = Context.Columns + .Where(h => h.Type == DeviceImportFieldTypes.IgnoreColumn && h.Name.IndexOf("serial", StringComparison.OrdinalIgnoreCase) >= 0); // All Values - possibleColumns = possibleColumns.Where(h => - { - return Context.RawData.Select(v => v[h.Item2]).All(v => !string.IsNullOrWhiteSpace(v)); - }).ToList(); + possibleColumns = possibleColumns.Where(h => DataReader.TestAllNotEmpty(h.Index)).ToList(); if (possibleColumns.Count() > 1) { @@ -74,17 +70,17 @@ namespace Disco.Services.Devices.Importing.Fields { try { - var top50SerialNumbers = Context.RawData.Select(v => v[h.Item2]).Take(50).ToArray(); + var top50SerialNumbers = DataReader.GetStrings(h.Index).Take(50).ToList(); return Database.Devices.Count(d => top50SerialNumbers.Contains(d.SerialNumber)) > 0; } catch (Exception) { return false; } - }).Select(h => (int?)h.Item2).FirstOrDefault(); + }).Select(h => (int?)h.Index).FirstOrDefault(); if (possibleColumnIndex.HasValue) return possibleColumnIndex; } - return possibleColumns.Select(h => (int?)h.Item2).FirstOrDefault(); + return possibleColumns.Select(h => (int?)h.Index).FirstOrDefault(); } public static string ParseRawDeviceSerialNumber(string DeviceSerialNumber) @@ -98,5 +94,6 @@ namespace Disco.Services.Devices.Importing.Fields { return DeviceSerialNumber != null && DeviceSerialNumber.Length <= maxLength; } + } } diff --git a/Disco.Services/Devices/Importing/Fields/ModelIdImportField.cs b/Disco.Services/Devices/Importing/Fields/ModelIdImportField.cs index 7ca8223e..cb9cf1bf 100644 --- a/Disco.Services/Devices/Importing/Fields/ModelIdImportField.cs +++ b/Disco.Services/Devices/Importing/Fields/ModelIdImportField.cs @@ -1,6 +1,7 @@ using Disco.Data.Repository; using Disco.Models.Repository; using Disco.Models.Services.Devices.Importing; +using Disco.Services.Interop.ActiveDirectory; using System; using System.Collections.Generic; using System.Data; @@ -20,23 +21,37 @@ namespace Disco.Services.Devices.Importing.Fields public override string FriendlyValue { get { return friendlyValue; } } public override string FriendlyPreviousValue { get { return friendlyPreviousValue; } } - public override bool Parse(DiscoDataContext Database, IDeviceImportCache Cache, DeviceImportContext Context, int RecordIndex, string DeviceSerialNumber, Device ExistingDevice, Dictionary Values, string Value) + public override bool Parse(DiscoDataContext Database, IDeviceImportCache Cache, IDeviceImportContext Context, string DeviceSerialNumber, Device ExistingDevice, List PreviousRecords, IDeviceImportDataReader DataReader, int ColumnIndex) { - friendlyValue = Value; + int? intValue; + if (DataReader.TryGetNullableInt(ColumnIndex, out intValue)) + { + if (!intValue.HasValue) + { + if (ExistingDevice == null) + { + intValue = 1; // Default Model for new devices + } + else + { + return Error("The Model Identifier cannot be blank"); + } + } - // Validate - if (string.IsNullOrWhiteSpace(Value)) - this.parsedValue = 1; // Default Model + parsedValue = intValue.Value; + friendlyValue = parsedValue.ToString(); + } else - if (!int.TryParse(Value, out parsedValue)) - return Error("The Model Identifier must be a number"); + { + return Error("The Model Identifier must be a number"); + } var m = Cache.DeviceModels.FirstOrDefault(dm => dm.Id == parsedValue); if (m == null) - return Error(string.Format("The identifier ({0}) does not match any Device Model", Value)); + return Error($"The identifier ({parsedValue}) does not match any Device Model"); - friendlyValue = string.Format("{0} [{1}]", m.Description, m.Id); + friendlyValue = $"{m.Description} [{m.Id}]"; if (ExistingDevice == null) return Success(EntityState.Added); @@ -46,7 +61,7 @@ namespace Disco.Services.Devices.Importing.Fields if (ExistingDevice.DeviceModelId.HasValue) { var previousModel = Cache.DeviceModels.FirstOrDefault(dm => dm.Id == ExistingDevice.DeviceModelId.Value); - friendlyPreviousValue = string.Format("{0} [{1}]", previousModel.Description, previousModel.Id); + friendlyPreviousValue = $"{previousModel.Description} [{previousModel.Id}]"; } return Success(EntityState.Modified); @@ -57,10 +72,10 @@ namespace Disco.Services.Devices.Importing.Fields public override bool Apply(DiscoDataContext Database, Device Device) { - if (this.FieldAction == EntityState.Added || - this.FieldAction == EntityState.Modified) + if (FieldAction == EntityState.Added || + FieldAction == EntityState.Modified) { - Device.DeviceModelId = this.parsedValue; + Device.DeviceModelId = parsedValue; return true; } else @@ -69,30 +84,43 @@ namespace Disco.Services.Devices.Importing.Fields } } - public override int? GuessHeader(DiscoDataContext Database, DeviceImportContext Context) + public override void Applied(DiscoDataContext Database, Device Device, ref bool DeviceADDescriptionSet) { - // 'model' in column name - var possibleColumns = Context.Header - .Select((h, i) => Tuple.Create(h, i)) - .Where(h => h.Item1.Item2 == DeviceImportFieldTypes.IgnoreColumn && h.Item1.Item1.IndexOf("model", System.StringComparison.OrdinalIgnoreCase) >= 0); + if (!DeviceADDescriptionSet) + { + if (ActiveDirectory.IsValidDomainAccountId(Device.DeviceDomainId)) + { + var adAccount = Device.ActiveDirectoryAccount(); + + if (adAccount != null && !adAccount.IsCriticalSystemObject) + { + adAccount.SetDescription(Device); + DeviceADDescriptionSet = true; + } + } + } + } + + public override int? GuessColumn(DiscoDataContext Database, IDeviceImportContext Context, IDeviceImportDataReader DataReader) + { + // column name + var possibleColumns = Context.Columns + .Where(h => h.Type == DeviceImportFieldTypes.IgnoreColumn && + h.Name.IndexOf("model", StringComparison.OrdinalIgnoreCase) >= 0); // All Integers Numbers - possibleColumns = possibleColumns.Where(h => - { - int lastValue; - return Context.RawData.Select(v => v[h.Item2]).Take(100).Where(v => !string.IsNullOrWhiteSpace(v)).All(v => int.TryParse(v, out lastValue)); - }).ToList(); + possibleColumns = possibleColumns.Where(h => DataReader.TestAllInt(h.Index)).ToList(); // Multiple Columns, tighten column definition if (possibleColumns.Count() > 1) { possibleColumns = possibleColumns .Where(h => - h.Item1.Item1.IndexOf("modelid", StringComparison.OrdinalIgnoreCase) >= 0 || - h.Item1.Item1.IndexOf("model id", StringComparison.OrdinalIgnoreCase) >= 0); + h.Name.IndexOf("modelid", StringComparison.OrdinalIgnoreCase) >= 0 || + h.Name.IndexOf("model id", StringComparison.OrdinalIgnoreCase) >= 0); } - return possibleColumns.Select(h => (int?)h.Item2).FirstOrDefault(); + return possibleColumns.Select(h => (int?)h.Index).FirstOrDefault(); } } } diff --git a/Disco.Services/Devices/Importing/Fields/ProfileIdImportField.cs b/Disco.Services/Devices/Importing/Fields/ProfileIdImportField.cs index f67660af..f90bb263 100644 --- a/Disco.Services/Devices/Importing/Fields/ProfileIdImportField.cs +++ b/Disco.Services/Devices/Importing/Fields/ProfileIdImportField.cs @@ -1,6 +1,7 @@ using Disco.Data.Repository; using Disco.Models.Repository; using Disco.Models.Services.Devices.Importing; +using Disco.Services.Interop.ActiveDirectory; using System; using System.Collections.Generic; using System.Data; @@ -20,30 +21,44 @@ namespace Disco.Services.Devices.Importing.Fields public override string FriendlyValue { get { return friendlyValue; } } public override string FriendlyPreviousValue { get { return friendlyPreviousValue; } } - public override bool Parse(DiscoDataContext Database, IDeviceImportCache Cache, DeviceImportContext Context, int RecordIndex, string DeviceSerialNumber, Device ExistingDevice, Dictionary Values, string Value) + public override bool Parse(DiscoDataContext Database, IDeviceImportCache Cache, IDeviceImportContext Context, string DeviceSerialNumber, Device ExistingDevice, List PreviousRecords, IDeviceImportDataReader DataReader, int ColumnIndex) { - friendlyValue = Value; + int? intValue; + if (DataReader.TryGetNullableInt(ColumnIndex, out intValue)) + { + if (!intValue.HasValue) + { + if (ExistingDevice == null) + { + intValue = Database.DiscoConfiguration.DeviceProfiles.DefaultAddDeviceOfflineDeviceProfileId; // Default Model for new devices + } + else + { + return Error("The Profile Identifier cannot be blank"); + } + } - // Validate - if (string.IsNullOrWhiteSpace(Value)) - this.parsedValue = Database.DiscoConfiguration.DeviceProfiles.DefaultAddDeviceOfflineDeviceProfileId; + parsedValue = intValue.Value; + friendlyValue = parsedValue.ToString(); + } else - if (!int.TryParse(Value, out parsedValue)) - return Error("The Profile Identifier must be a number"); + { + return Error("The Profile Identifier must be a number"); + } var p = Cache.DeviceProfiles.FirstOrDefault(dp => dp.Id == parsedValue); if (p == null) - return Error(string.Format("The identifier ({0}) does not match any Device Profile", Value)); + return Error($"The identifier ({parsedValue}) does not match any Device Profile"); - friendlyValue = string.Format("{0} [{1}]", p.Description, p.Id); + friendlyValue = $"{p.Description} [{p.Id}]"; if (ExistingDevice == null) return Success(EntityState.Added); else if (ExistingDevice != null && ExistingDevice.DeviceProfileId != parsedValue) { var previousProfile = Cache.DeviceProfiles.FirstOrDefault(dp => dp.Id == ExistingDevice.DeviceProfileId); - friendlyPreviousValue = string.Format("{0} [{1}]", previousProfile.Description, previousProfile.Id); + friendlyPreviousValue = $"{previousProfile.Description} [{previousProfile.Id}]"; return Success(EntityState.Modified); } @@ -53,10 +68,10 @@ namespace Disco.Services.Devices.Importing.Fields public override bool Apply(DiscoDataContext Database, Device Device) { - if (this.FieldAction == EntityState.Added || - this.FieldAction == EntityState.Modified) + if (FieldAction == EntityState.Added || + FieldAction == EntityState.Modified) { - Device.DeviceProfileId = this.parsedValue; + Device.DeviceProfileId = parsedValue; return true; } else @@ -65,30 +80,43 @@ namespace Disco.Services.Devices.Importing.Fields } } - public override int? GuessHeader(DiscoDataContext Database, DeviceImportContext Context) + public override void Applied(DiscoDataContext Database, Device Device, ref bool DeviceADDescriptionSet) + { + if (!DeviceADDescriptionSet) + { + if (ActiveDirectory.IsValidDomainAccountId(Device.DeviceDomainId)) + { + var adAccount = Device.ActiveDirectoryAccount(); + + if (adAccount != null && !adAccount.IsCriticalSystemObject) + { + adAccount.SetDescription(Device); + DeviceADDescriptionSet = true; + } + } + } + } + + public override int? GuessColumn(DiscoDataContext Database, IDeviceImportContext Context, IDeviceImportDataReader DataReader) { // column name - var possibleColumns = Context.Header - .Select((h, i) => Tuple.Create(h, i)) - .Where(h => h.Item1.Item2 == DeviceImportFieldTypes.IgnoreColumn && h.Item1.Item1.IndexOf("profile", System.StringComparison.OrdinalIgnoreCase) >= 0); + var possibleColumns = Context.Columns + .Where(h => h.Type == DeviceImportFieldTypes.IgnoreColumn && + h.Name.IndexOf("profile", StringComparison.OrdinalIgnoreCase) >= 0); // All Integers Numbers - possibleColumns = possibleColumns.Where(h => - { - int lastValue; - return Context.RawData.Select(v => v[h.Item2]).Take(100).Where(v => !string.IsNullOrWhiteSpace(v)).All(v => int.TryParse(v, out lastValue)); - }).ToList(); + possibleColumns = possibleColumns.Where(h => DataReader.TestAllInt(h.Index)).ToList(); // Multiple Columns, tighten column definition if (possibleColumns.Count() > 1) { possibleColumns = possibleColumns .Where(h => - h.Item1.Item1.IndexOf("profileid", StringComparison.OrdinalIgnoreCase) >= 0 || - h.Item1.Item1.IndexOf("profile id", StringComparison.OrdinalIgnoreCase) >= 0); + h.Name.IndexOf("profileid", StringComparison.OrdinalIgnoreCase) >= 0 || + h.Name.IndexOf("profile id", StringComparison.OrdinalIgnoreCase) >= 0); } - return possibleColumns.Select(h => (int?)h.Item2).FirstOrDefault(); + return possibleColumns.Select(h => (int?)h.Index).FirstOrDefault(); } } } diff --git a/Disco.Services/Devices/Importing/XlsxDeviceImportContext.cs b/Disco.Services/Devices/Importing/XlsxDeviceImportContext.cs new file mode 100644 index 00000000..445156bb --- /dev/null +++ b/Disco.Services/Devices/Importing/XlsxDeviceImportContext.cs @@ -0,0 +1,133 @@ +using ClosedXML.Excel; +using Disco.Models.Services.Devices.Importing; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; + +namespace Disco.Services.Devices.Importing +{ + public class XlsxDeviceImportContext : BaseDeviceImportContext + { + private bool hasHeaderRow; + private List rawData; + + public XlsxDeviceImportContext(string Filename, bool HasHeaderRow, Stream XlsxStream) + : base(Filename) + { + hasHeaderRow = HasHeaderRow; + + using (var stream = new MemoryStream((int)XlsxStream.Length)) + { + XlsxStream.CopyTo(stream); + + ParseXlsx(stream); + } + } + + public override int RecordCount + { + get + { + return rawData.Count; + } + } + + public override IDeviceImportDataReader GetDataReader() + { + return new XlsxDeviceImportDataReader(this, rawData, hasHeaderRow); + } + + private void ParseXlsx(Stream XlsxStream) + { + using (var xlWorkbook = new XLWorkbook(XlsxStream, XLEventTracking.Disabled)) + { + if (xlWorkbook.Worksheets.Count == 0) + throw new IndexOutOfRangeException("Workbook contains no worksheets"); + + // Use first worksheet + var worksheet = xlWorkbook.Worksheets.Worksheet(1); + + SetDatasetName($"{Filename} [Sheet: {worksheet.Name}]"); + + var columnCount = worksheet.LastColumnUsed().ColumnNumber(); + + if (hasHeaderRow) + { + var headerRow = worksheet.FirstRow(); + SetColumns(Enumerable.Range(1, columnCount) + .Select(i => + { + var cell = headerRow.Cell(i); + var headerName = cell.GetString(); + if (string.IsNullOrWhiteSpace(headerName)) + { + headerName = $"Column {cell.WorksheetColumn().ColumnLetter()}"; + } + return new DeviceImportColumn() + { + Index = i - 1, + Name = headerName, + Type = DeviceImportFieldTypes.IgnoreColumn + }; + })); + } + else + { + SetColumns(Enumerable.Range(1, columnCount) + .Select(i => new DeviceImportColumn() + { + Index = i - 1, + Name = $"Column {worksheet.Column(i).ColumnLetter()}", + Type = DeviceImportFieldTypes.IgnoreColumn + })); + } + + // Import Data + var rawData = new List(); + foreach (var row in worksheet.RowsUsed().Skip(hasHeaderRow ? 1 : 0)) + { + var record = new object[columnCount]; + rawData.Add(record); + + for (int columnIndex = 0; columnIndex < columnCount; columnIndex++) + { + var cell = row.Cell(columnIndex + 1); + var cellValue = cell.Value; + + switch (cell.DataType) + { + case XLCellValues.Number: + if (cellValue is double) + { + record[columnIndex] = (double)cellValue; + } + continue; + case XLCellValues.Boolean: + if (cellValue is bool) + { + record[columnIndex] = (bool)cellValue; + } + continue; + case XLCellValues.DateTime: + if (cellValue is DateTime) + { + record[columnIndex] = (DateTime)cellValue; + } + continue; + } + + var stringValue = cellValue == null ? null : cellValue.ToString(); + if (stringValue != null && stringValue.Length == 0) + { + stringValue = null; + } + record[columnIndex] = stringValue; + } + } + + this.rawData = rawData; + } + } + } +} diff --git a/Disco.Services/Devices/Importing/XlsxDeviceImportDataReader.cs b/Disco.Services/Devices/Importing/XlsxDeviceImportDataReader.cs new file mode 100644 index 00000000..800f4708 --- /dev/null +++ b/Disco.Services/Devices/Importing/XlsxDeviceImportDataReader.cs @@ -0,0 +1,264 @@ +using Disco.Models.Services.Devices.Importing; +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Linq; + +namespace Disco.Services.Devices.Importing +{ + public class XlsxDeviceImportDataReader : IDeviceImportDataReader + { + private static string[] TrueValues = { "true", "1", "yes", "-1", "on" }; + private static string[] FalseValues = { "false", "0", "no", "off" }; + + private XlsxDeviceImportContext context; + private List rawData; + private int currentRowIndex; + private int rowOffset; + private object[] currentRow; + + public int Index { get { return currentRowIndex; } } + + public XlsxDeviceImportDataReader(XlsxDeviceImportContext Context, List RawData, bool HasHeaderRow) + { + context = Context; + rawData = RawData; + currentRowIndex = 0; + rowOffset = currentRowIndex = HasHeaderRow ? 2 : 1; + } + + public void Reset() + { + currentRowIndex = 0; + currentRow = null; + } + + public bool Read() + { + if (++currentRowIndex >= rawData.Count) + { + currentRowIndex--; + return false; + } + + currentRow = rawData[currentRowIndex]; + return true; + } + + public int GetRowNumber(int Index) + { + return Index + rowOffset; + } + + public string GetString(int ColumnIndex) + { + if (currentRow == null) + throw new InvalidOperationException($"{nameof(XlsxDeviceImportDataReader.Read)} must be called before retrieving values"); + + var cell = currentRow[ColumnIndex]; + + if (cell == null) + return null; + else + return cell.ToString(); + } + + public IEnumerable GetStrings(int ColumnIndex) + { + return rawData.Select(r => r[ColumnIndex]?.ToString()); + } + + public bool TryGetNullableInt(int ColumnIndex, out int? value) + { + if (currentRow == null) + throw new InvalidOperationException($"{nameof(XlsxDeviceImportDataReader.Read)} must be called before retrieving values"); + + return TryGetNullableInt(currentRow[ColumnIndex], out value); + } + + public bool TryGetNullableBool(int ColumnIndex, out bool? value) + { + if (currentRow == null) + throw new InvalidOperationException($"{nameof(XlsxDeviceImportDataReader.Read)} must be called before retrieving values"); + + return TryGetNullableBool(currentRow[ColumnIndex], out value); + } + + public bool TryGetNullableDateTime(int ColumnIndex, out DateTime? value) + { + if (currentRow == null) + throw new InvalidOperationException($"{nameof(XlsxDeviceImportDataReader.Read)} must be called before retrieving values"); + + return TryGetNullableDateTime(currentRow[ColumnIndex], out value); + } + + public bool TestAllNotEmpty(int ColumnIndex) + { + return GetStrings(ColumnIndex).All(s => !string.IsNullOrWhiteSpace(s)); + } + + public bool TestAllNullableInt(int ColumnIndex) + { + return rawData.Select(r => r[ColumnIndex]) + .All(c => + { + int? value; + return TryGetNullableInt(c, out value); + }); + } + + public bool TestAllInt(int ColumnIndex) + { + return rawData.Select(r => r[ColumnIndex]) + .All(c => + { + int? value; + return TryGetNullableInt(c, out value) && value.HasValue; + }); + } + + public bool TestAllNullableBool(int ColumnIndex) + { + return rawData.Select(r => r[ColumnIndex]) + .All(c => + { + bool? value; + return TryGetNullableBool(c, out value); + }); + } + + public bool TestAllNullableDateTime(int ColumnIndex) + { + return rawData.Select(r => r[ColumnIndex]) + .All(c => + { + DateTime? value; + return TryGetNullableDateTime(c, out value); + }); + } + + public void Dispose() + { + // Nothing to dispose + } + + private bool TryGetNullableDateTime(object Content, out DateTime? value) + { + if (Content == null) + { + value = null; + return true; + } + + if (Content is DateTime) + { + value = (DateTime)Content; + return true; + } + + var stringValue = Content.ToString(); + + if (string.IsNullOrWhiteSpace(stringValue)) + { + value = null; + return true; + } + else + { + stringValue = stringValue.Trim(); + + DateTime valueDateTime; + if (DateTime.TryParse(stringValue, CultureInfo.CurrentCulture, DateTimeStyles.AssumeLocal, out valueDateTime)) + { + value = valueDateTime; + return true; + } + else + { + value = null; + return false; + } + } + } + + private bool TryGetNullableBool(object Content, out bool? value) + { + if (Content == null) + { + value = null; + return true; + } + + if (Content is bool) + { + value = (bool)Content; + return true; + } + + var stringValue = Content.ToString(); + + if (string.IsNullOrWhiteSpace(stringValue)) + { + value = null; + return true; + } + else + { + stringValue = stringValue.Trim(); + + if (TrueValues.Contains(stringValue, StringComparer.OrdinalIgnoreCase)) + { + value = true; + return true; + } + else if (FalseValues.Contains(stringValue, StringComparer.OrdinalIgnoreCase)) + { + value = false; + return true; + } + else + { + value = null; + return false; + } + } + } + + private bool TryGetNullableInt(object Content, out int? value) + { + if (Content == null) + { + value = null; + return true; + } + + if (Content is double) + { + value = (int)((double)Content); + return true; + } + + var stringValue = Content.ToString(); + + if (string.IsNullOrWhiteSpace(stringValue)) + { + value = null; + return true; + } + else + { + int intValue; + if (int.TryParse(stringValue, out intValue)) + { + value = intValue; + return true; + } + else + { + value = null; + return false; + } + } + } + } +} diff --git a/Disco.Services/Disco.Services.csproj b/Disco.Services/Disco.Services.csproj index d42a2af0..182e4051 100644 --- a/Disco.Services/Disco.Services.csproj +++ b/Disco.Services/Disco.Services.csproj @@ -36,6 +36,14 @@ false + + ..\packages\ClosedXML.0.85.0\lib\net40\ClosedXML.dll + True + + + ..\packages\DocumentFormat.OpenXml.2.5\lib\DocumentFormat.OpenXml.dll + True + ..\packages\EntityFramework.5.0.0\lib\net45\EntityFramework.dll @@ -244,11 +252,14 @@ + + - + + @@ -269,6 +280,8 @@ + + diff --git a/Disco.Services/Plugins/PluginManifest.cs b/Disco.Services/Plugins/PluginManifest.cs index b15bdf1b..b3b4dc37 100644 --- a/Disco.Services/Plugins/PluginManifest.cs +++ b/Disco.Services/Plugins/PluginManifest.cs @@ -4,8 +4,6 @@ using System.IO; using System.Linq; using System.Reflection; using System.Security.Cryptography; -using System.Text; -using System.Threading.Tasks; using System.Web; using System.Web.Mvc; using Disco.Data.Repository; @@ -81,11 +79,11 @@ namespace Disco.Services.Plugins public List GetFeatures(Type FeatureCategoryType) { - return this.Features.Where(fm => fm.CategoryType.IsAssignableFrom(FeatureCategoryType)).ToList(); + return Features.Where(fm => fm.CategoryType.IsAssignableFrom(FeatureCategoryType)).ToList(); } public PluginFeatureManifest GetFeature(string PluginFeatureId) { - return this.Features.Where(fm => fm.Id == PluginFeatureId).FirstOrDefault(); + return Features.Where(fm => fm.Id == PluginFeatureId).FirstOrDefault(); } public Plugin CreateInstance() @@ -135,6 +133,7 @@ namespace Disco.Services.Plugins return new List() { "C5", + "ClosedXML", "Common.Logging", "Disco.BI", "Disco.Data", @@ -142,6 +141,7 @@ namespace Disco.Services.Plugins "Disco.Services", "Disco.Web", "Disco.Web.Extensions", + "DocumentFormat.OpenXml", "EntityFramework", "Exceptionless", "Exceptionless.Models", @@ -159,7 +159,7 @@ namespace Disco.Services.Plugins "Owin", "PdfiumViewer", "PdfSharp", - "PList", + "PListNet", "Quartz", "RazorGenerator.Mvc", "Renci.SshNet", @@ -183,7 +183,7 @@ namespace Disco.Services.Plugins "System.Web.WebPages.Razor", "T4MVCExtensions", "WebActivatorEx", - "zxing" + "ZXingNet" }; }); public static IReadOnlyCollection PluginExcludedAssemblies @@ -297,34 +297,34 @@ namespace Disco.Services.Plugins if (!environmentInitalized) { - var assemblyFullPath = Path.Combine(this.PluginLocation, this.AssemblyPath); + var assemblyFullPath = Path.Combine(PluginLocation, AssemblyPath); if (!File.Exists(assemblyFullPath)) - throw new FileNotFoundException(string.Format("Plugin Assembly [{0}] not found at: {1}", this.Id, assemblyFullPath), assemblyFullPath); + throw new FileNotFoundException(string.Format("Plugin Assembly [{0}] not found at: {1}", Id, assemblyFullPath), assemblyFullPath); - if (this.PluginAssembly == null) - this.PluginAssembly = Assembly.LoadFile(assemblyFullPath); + if (PluginAssembly == null) + PluginAssembly = Assembly.LoadFile(assemblyFullPath); - if (this.PluginAssembly == null) - throw new InvalidOperationException(string.Format("Unable to load Plugin Assembly [{0}] at: {1}", this.Id, assemblyFullPath)); + if (PluginAssembly == null) + throw new InvalidOperationException(string.Format("Unable to load Plugin Assembly [{0}] at: {1}", Id, assemblyFullPath)); - PluginsLog.LogInitializingPluginAssembly(this.PluginAssembly); + PluginsLog.LogInitializingPluginAssembly(PluginAssembly); // Check Manifest/Assembly Versions Match - if (this.Version != this.PluginAssembly.GetName().Version) - throw new InvalidOperationException(string.Format("The plugin [{0}] manifest version [{1}] doesn't match the plugin assembly [{2} : {3}]", this.Id, this.Version, assemblyFullPath, this.PluginAssembly.GetName().Version)); + if (Version != PluginAssembly.GetName().Version) + throw new InvalidOperationException(string.Format("The plugin [{0}] manifest version [{1}] doesn't match the plugin assembly [{2} : {3}]", Id, Version, assemblyFullPath, PluginAssembly.GetName().Version)); - if (this.Type == null) - this.Type = this.PluginAssembly.GetType(this.TypeName, true, true); + if (Type == null) + Type = PluginAssembly.GetType(TypeName, true, true); - if (this.ConfigurationHandlerType == null) - this.ConfigurationHandlerType = this.PluginAssembly.GetType(this.ConfigurationHandlerTypeName, true, true); + if (ConfigurationHandlerType == null) + ConfigurationHandlerType = PluginAssembly.GetType(ConfigurationHandlerTypeName, true, true); - if (!string.IsNullOrEmpty(this.WebHandlerTypeName) && this.WebHandlerType == null) - this.WebHandlerType = this.PluginAssembly.GetType(this.WebHandlerTypeName, true, true); + if (!string.IsNullOrEmpty(WebHandlerTypeName) && WebHandlerType == null) + WebHandlerType = PluginAssembly.GetType(WebHandlerTypeName, true, true); // Update non-static values - this.StorageLocation = Path.Combine(Database.DiscoConfiguration.PluginStorageLocation, this.Id); + StorageLocation = Path.Combine(Database.DiscoConfiguration.PluginStorageLocation, Id); environmentInitalized = true; } @@ -336,7 +336,7 @@ namespace Disco.Services.Plugins // Initialize Plugin InitializePluginEnvironment(Database); - using (var pluginInstance = this.CreateInstance()) + using (var pluginInstance = CreateInstance()) { pluginInstance.AfterUpdate(Database, PreviousManifest); } @@ -348,7 +348,7 @@ namespace Disco.Services.Plugins // Initialize Plugin InitializePluginEnvironment(Database); - using (var pluginInstance = this.CreateInstance()) + using (var pluginInstance = CreateInstance()) { pluginInstance.Uninstall(Database, UninstallData, Status); } @@ -360,7 +360,7 @@ namespace Disco.Services.Plugins // Initialize Plugin InitializePluginEnvironment(Database); - using (var pluginInstance = this.CreateInstance()) + using (var pluginInstance = CreateInstance()) { pluginInstance.Install(Database, Status); } @@ -373,7 +373,7 @@ namespace Disco.Services.Plugins InitializePluginEnvironment(Database); // Initialize Plugin - using (var pluginInstance = this.CreateInstance()) + using (var pluginInstance = CreateInstance()) { pluginInstance.Initialize(Database); } @@ -398,12 +398,12 @@ namespace Disco.Services.Plugins public PluginConfigurationHandler CreateConfigurationHandler() { // Configuration Handler is Required - if (this.ConfigurationHandlerType == null) + if (ConfigurationHandlerType == null) throw new ArgumentNullException("ConfigurationType"); - if (!typeof(PluginConfigurationHandler).IsAssignableFrom(this.ConfigurationHandlerType)) + if (!typeof(PluginConfigurationHandler).IsAssignableFrom(ConfigurationHandlerType)) throw new ArgumentException("The Plugin ConfigurationHandlerType must inherit Disco.Services.Plugins.PluginConfigurationHandler", "ConfigurationHandlerType"); - var handler = (PluginConfigurationHandler)Activator.CreateInstance(this.ConfigurationHandlerType); + var handler = (PluginConfigurationHandler)Activator.CreateInstance(ConfigurationHandlerType); handler.Manifest = this; @@ -414,7 +414,7 @@ namespace Disco.Services.Plugins { get { - return string.Format("/Config/Plugins/{0}", HttpUtility.UrlEncode(this.Id)); + return string.Format("/Config/Plugins/{0}", HttpUtility.UrlEncode(Id)); } } [JsonIgnore] @@ -422,31 +422,31 @@ namespace Disco.Services.Plugins { get { - return this.WebHandlerType != null; + return WebHandlerType != null; } } public PluginWebHandler CreateWebHandler(Controller HostController) { // Web Handler is Not Required - if (this.WebHandlerType == null) + if (WebHandlerType == null) return null; - if (!typeof(PluginWebHandler).IsAssignableFrom(this.WebHandlerType)) + if (!typeof(PluginWebHandler).IsAssignableFrom(WebHandlerType)) throw new ArgumentException("The Plugin WebHandlerType must inherit Disco.Services.Plugins.PluginWebHandler", "WebHandlerType"); // Determine WebHandler Authorize Attributes - if (this.WebHandlerAuthorizers == null) + if (WebHandlerAuthorizers == null) { - this.WebHandlerAuthorizers = this.WebHandlerType.GetCustomAttributes(true).ToArray(); + WebHandlerAuthorizers = WebHandlerType.GetCustomAttributes(true).ToArray(); } - if (this.WebHandlerAuthorizers.Length > 0) + if (WebHandlerAuthorizers.Length > 0) { - var attributeDenied = this.WebHandlerAuthorizers.FirstOrDefault(a => !a.IsAuthorized(HostController.HttpContext)); + var attributeDenied = WebHandlerAuthorizers.FirstOrDefault(a => !a.IsAuthorized(HostController.HttpContext)); if (attributeDenied != null) - throw new AccessDeniedException(attributeDenied.HandleUnauthorizedMessage(), string.Format("[Plugin]::{0}::[Handler]", this.Id)); + throw new AccessDeniedException(attributeDenied.HandleUnauthorizedMessage(), string.Format("[Plugin]::{0}::[Handler]", Id)); } - var handler = (PluginWebHandler)Activator.CreateInstance(this.WebHandlerType); + var handler = (PluginWebHandler)Activator.CreateInstance(WebHandlerType); handler.Manifest = this; handler.HostController = HostController; @@ -458,7 +458,7 @@ namespace Disco.Services.Plugins { get { - return string.Format("/Plugin/{0}", HttpUtility.UrlEncode(this.Id)); + return string.Format("/Plugin/{0}", HttpUtility.UrlEncode(Id)); } } public string WebActionUrl(string Action) @@ -467,7 +467,7 @@ namespace Disco.Services.Plugins throw new NotSupportedException("This plugin doesn't have a web handler"); var url = UrlHelper.GenerateUrl("Plugin", null, null, - new RouteValueDictionary(new Dictionary() { { "PluginId", this.Id }, { "PluginAction", Action } }), + new RouteValueDictionary(new Dictionary() { { "PluginId", Id }, { "PluginAction", Action } }), RouteTable.Routes, HttpContext.Current.Request.RequestContext, false); return url; @@ -481,17 +481,17 @@ namespace Disco.Services.Plugins if (Resource.Contains("..")) throw new ArgumentException("Resource Paths cannot navigate to the parent", "Resource"); - var resourcePath = Path.Combine(this.PluginLocation, "WebResources", Resource.Replace(@"/", @"\")); + var resourcePath = Path.Combine(PluginLocation, "WebResources", Resource.Replace(@"/", @"\")); Tuple resourceHash; - string resourceKey = string.Format("{0}://{1}", this.Name, Resource); + string resourceKey = string.Format("{0}://{1}", Name, Resource); if (WebResourceHashes.TryGetValue(resourceKey, out resourceHash)) { #if DEBUG var fileDateCheck = System.IO.File.GetLastWriteTime(resourcePath); if (fileDateCheck == resourceHash.Item2) #endif - return new Tuple(resourcePath, resourceHash.Item1); + return new Tuple(resourcePath, resourceHash.Item1); } if (!File.Exists(resourcePath)) @@ -513,10 +513,10 @@ namespace Disco.Services.Plugins } public string WebResourceUrl(string Resource) { - var resourcePath = this.WebResourcePath(Resource); + var resourcePath = WebResourcePath(Resource); var url = UrlHelper.GenerateUrl("Plugin_Resources", null, null, - new RouteValueDictionary(new Dictionary() { { "PluginId", this.Id }, { "res", Resource } }), + new RouteValueDictionary(new Dictionary() { { "PluginId", Id }, { "res", Resource } }), RouteTable.Routes, HttpContext.Current.Request.RequestContext, false); url += string.Format("?v={0}", resourcePath.Item2); @@ -526,7 +526,7 @@ namespace Disco.Services.Plugins public void LogException(Exception PluginException) { - PluginsLog.LogPluginException(this.ToString(), PluginException); + PluginsLog.LogPluginException(ToString(), PluginException); } public void LogWarning(string Message) { @@ -547,7 +547,7 @@ namespace Disco.Services.Plugins public override string ToString() { - return string.Format("{0} [{1} v{2}]", this.Name, this.Id, this.VersionFormatted); + return string.Format("{0} [{1} v{2}]", Name, Id, VersionFormatted); } } } diff --git a/Disco.Services/packages.config b/Disco.Services/packages.config index 2e7bdf91..4cf5d546 100644 --- a/Disco.Services/packages.config +++ b/Disco.Services/packages.config @@ -1,5 +1,7 @@  + + diff --git a/Disco.Web.Extensions/Disco.Web.Extensions.csproj b/Disco.Web.Extensions/Disco.Web.Extensions.csproj index 619bddc9..75525ca1 100644 --- a/Disco.Web.Extensions/Disco.Web.Extensions.csproj +++ b/Disco.Web.Extensions/Disco.Web.Extensions.csproj @@ -103,6 +103,8 @@ + + diff --git a/Disco.Web.Extensions/MvcExtensions/File/FileContentSpanResult.cs b/Disco.Web.Extensions/MvcExtensions/File/FileContentSpanResult.cs new file mode 100644 index 00000000..b71210b2 --- /dev/null +++ b/Disco.Web.Extensions/MvcExtensions/File/FileContentSpanResult.cs @@ -0,0 +1,34 @@ +using System; +using System.Web; +using System.Web.Mvc; + +namespace Disco.Web.Extensions +{ + public class FileContentSpanResult : FileResult + { + public byte[] FileBuffer { get; private set; } + public int Start { get; private set; } + public int Length { get; private set; } + + public FileContentSpanResult(byte[] fileBuffer, int start, int length, string contentType) : base(contentType) + { + if (fileBuffer == null) + throw new ArgumentNullException(nameof(fileBuffer)); + + if (start < 0 || start >= fileBuffer.Length) + throw new ArgumentOutOfRangeException(nameof(start)); + + if (start + length > fileBuffer.Length) + throw new ArgumentOutOfRangeException(nameof(length)); + + FileBuffer = fileBuffer; + Start = start; + Length = length; + } + + protected override void WriteFile(HttpResponseBase response) + { + response.OutputStream.Write(this.FileBuffer, Start, Length); + } + } +} diff --git a/Disco.Web.Extensions/MvcExtensions/File/FileExtensions.cs b/Disco.Web.Extensions/MvcExtensions/File/FileExtensions.cs new file mode 100644 index 00000000..97089d7e --- /dev/null +++ b/Disco.Web.Extensions/MvcExtensions/File/FileExtensions.cs @@ -0,0 +1,17 @@ +using System.Web.Mvc; + +namespace Disco.Web.Extensions +{ + public static class FileExtensions + { + + public static FileContentSpanResult File(this IController controller, byte[] fileBuffer, int start, int length, string contentType, string fileDownloadName) + { + return new FileContentSpanResult(fileBuffer, start, length, contentType) + { + FileDownloadName = fileDownloadName + }; + } + + } +} diff --git a/Disco.Web/Areas/API/Controllers/DeviceController.cs b/Disco.Web/Areas/API/Controllers/DeviceController.cs index 46662c5c..45e16942 100644 --- a/Disco.Web/Areas/API/Controllers/DeviceController.cs +++ b/Disco.Web/Areas/API/Controllers/DeviceController.cs @@ -11,6 +11,7 @@ using Disco.Services.Interop; using Disco.Services.Interop.ActiveDirectory; using Disco.Services.Users; using Disco.Services.Web; +using Disco.Web.Extensions; using Disco.Web.Models.Device; using System; using System.Collections.Generic; @@ -611,15 +612,15 @@ namespace Disco.Web.Areas.API.Controllers #region Importing internal const string ImportSessionCacheKey = "DeviceImportContext_{0}"; - internal static void Import_StoreContext(DeviceImportContext Context) + internal static void Import_StoreContext(IDeviceImportContext Context) { string key = string.Format(ImportSessionCacheKey, Context.SessionId); HttpRuntime.Cache.Insert(key, Context, null, DateTime.Now.AddMinutes(60), Cache.NoSlidingExpiration, CacheItemPriority.NotRemovable, null); } - internal static DeviceImportContext Import_RetrieveContext(string SessionId, bool Remove = false) + internal static IDeviceImportContext Import_RetrieveContext(string SessionId, bool Remove = false) { string key = string.Format(ImportSessionCacheKey, SessionId); - DeviceImportContext context = HttpRuntime.Cache.Get(key) as DeviceImportContext; + IDeviceImportContext context = HttpRuntime.Cache.Get(key) as IDeviceImportContext; if (Remove && context != null) HttpRuntime.Cache.Remove(key); @@ -654,7 +655,7 @@ namespace Disco.Web.Areas.API.Controllers if (context == null) throw new ArgumentException("The Import Session Id is invalid or the session timed out (60 minutes), try importing again", "Id"); - context.UpdateHeaderTypes(Headers); + context.UpdateColumnTypes(Headers); var status = DeviceImportParseTask.ScheduleNow(context); @@ -729,12 +730,25 @@ namespace Disco.Web.Areas.API.Controllers if (context == null) throw new ArgumentException("The Id specified is invalid, or the export data expired (60 minutes)", "Id"); - if (context.Result == null || context.Result.CsvResult == null) + if (context.Result == null || context.Result.Result == null) throw new ArgumentException("The export session is still running, or failed to complete successfully", "Id"); - var filename = string.Format("DiscoDeviceExport-{0:yyyyMMdd-HHmmss}.csv", context.TaskStatus.StartedTimestamp.Value); + string filename; + string mimeType; + if (context.Options.ExcelFormat) + { + filename = $"DiscoDeviceExport-{context.TaskStatus.StartedTimestamp.Value:yyyyMMdd-HHmmss}.xlsx"; + mimeType = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"; + } + else + { + filename = $"DiscoDeviceExport-{context.TaskStatus.StartedTimestamp.Value:yyyyMMdd-HHmmss}.csv"; + mimeType = "text/csv"; + } - return File(context.Result.CsvResult.ToArray(), "text/csv", filename); + var fileStream = context.Result.Result; + + return this.File(fileStream.GetBuffer(), 0, (int)fileStream.Length, mimeType, filename); } #endregion diff --git a/Disco.Web/Models/Device/ImportModel.cs b/Disco.Web/Models/Device/ImportModel.cs index 48cff03c..8fe32c31 100644 --- a/Disco.Web/Models/Device/ImportModel.cs +++ b/Disco.Web/Models/Device/ImportModel.cs @@ -11,7 +11,7 @@ namespace Disco.Web.Models.Device { public class ImportModel : DeviceImportModel { - [Required, Display(Name = "CSV Import File")] + [Required, Display(Name = "Import File")] public HttpPostedFileBase ImportFile { get; set; } [Required, Display(Name = "Has Header")] diff --git a/Disco.Web/Views/Device/Export.cshtml b/Disco.Web/Views/Device/Export.cshtml index 55dae634..c5aa9d44 100644 --- a/Disco.Web/Views/Device/Export.cshtml +++ b/Disco.Web/Views/Device/Export.cshtml @@ -35,7 +35,7 @@   - @Html.CheckBoxFor(m => m.Options.ExcelCsvFormat) + @Html.CheckBoxFor(m => m.Options.ExcelFormat) diff --git a/Disco.Web/Views/Device/Export.generated.cs b/Disco.Web/Views/Device/Export.generated.cs index bdf262ed..5b47e8fa 100644 --- a/Disco.Web/Views/Device/Export.generated.cs +++ b/Disco.Web/Views/Device/Export.generated.cs @@ -2,7 +2,7 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. -// Runtime Version:4.0.30319.34014 +// Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -27,7 +27,6 @@ namespace Disco.Web.Views.Device using System.Web.UI; using System.Web.WebPages; using Disco; - using Disco.BI.Extensions; using Disco.Models.Repository; #line 2 "..\..\Views\Device\Export.cshtml" @@ -173,17 +172,17 @@ WriteLiteral(" "); #line 38 "..\..\Views\Device\Export.cshtml" - Write(Html.CheckBoxFor(m => m.Options.ExcelCsvFormat)); + Write(Html.CheckBoxFor(m => m.Options.ExcelFormat)); #line default #line hidden WriteLiteral(" Microsoft Excel CSV Format\r\n \r\n <" + -"/tr>\r\n \r\n \r\n"); +WriteLiteral(">Microsoft Excel Format\r\n \r\n " + +"\r\n \r\n \r\n"); WriteLiteral(" \r\n"); #line hidden WriteLiteral(" (optionItem.Description +, Tuple.Create(Tuple.Create("", 3831), Tuple.Create(optionItem.Description #line default #line hidden -, 3841), false) +, 3831), false) ); WriteLiteral(">\r\n (optionItem.PropertyName + , Tuple.Create(Tuple.Create("", 3949), Tuple.Create(optionItem.PropertyName #line default #line hidden -, 3959), false) +, 3949), false) ); -WriteAttribute("name", Tuple.Create(" name=\"", 3984), Tuple.Create("\"", 4023) -, Tuple.Create(Tuple.Create("", 3991), Tuple.Create("Options.", 3991), true) +WriteAttribute("name", Tuple.Create(" name=\"", 3974), Tuple.Create("\"", 4013) +, Tuple.Create(Tuple.Create("", 3981), Tuple.Create("Options.", 3981), true) #line 67 "..\..\Views\Device\Export.cshtml" - , Tuple.Create(Tuple.Create("", 3999), Tuple.Create(optionItem.PropertyName + , Tuple.Create(Tuple.Create("", 3989), Tuple.Create(optionItem.PropertyName #line default #line hidden -, 3999), false) +, 3989), false) ); WriteLiteral(" value=\"true\""); @@ -360,15 +359,15 @@ WriteLiteral(" "); #line hidden WriteLiteral("/>(optionItem.PropertyName + , Tuple.Create(Tuple.Create("", 4097), Tuple.Create(optionItem.PropertyName #line default #line hidden -, 4107), false) +, 4097), false) ); WriteLiteral(">"); @@ -416,40 +415,40 @@ WriteLiteral(">\r\n"); #line hidden WriteLiteral(" (optionItem.Description +, Tuple.Create(Tuple.Create("", 4664), Tuple.Create(optionItem.Description #line default #line hidden -, 4674), false) +, 4664), false) ); WriteLiteral(">\r\n (optionItem.PropertyName + , Tuple.Create(Tuple.Create("", 4782), Tuple.Create(optionItem.PropertyName #line default #line hidden -, 4792), false) +, 4782), false) ); -WriteAttribute("name", Tuple.Create(" name=\"", 4817), Tuple.Create("\"", 4856) -, Tuple.Create(Tuple.Create("", 4824), Tuple.Create("Options.", 4824), true) +WriteAttribute("name", Tuple.Create(" name=\"", 4807), Tuple.Create("\"", 4846) +, Tuple.Create(Tuple.Create("", 4814), Tuple.Create("Options.", 4814), true) #line 76 "..\..\Views\Device\Export.cshtml" - , Tuple.Create(Tuple.Create("", 4832), Tuple.Create(optionItem.PropertyName + , Tuple.Create(Tuple.Create("", 4822), Tuple.Create(optionItem.PropertyName #line default #line hidden -, 4832), false) +, 4822), false) ); WriteLiteral(" value=\"true\""); @@ -465,15 +464,15 @@ WriteLiteral(" "); #line hidden WriteLiteral("/>(optionItem.PropertyName + , Tuple.Create(Tuple.Create("", 4930), Tuple.Create(optionItem.PropertyName #line default #line hidden -, 4940), false) +, 4930), false) ); WriteLiteral(">"); @@ -604,14 +603,14 @@ WriteLiteral(" record"); #line hidden WriteLiteral(" were successfully exported.\r\n (Url.Action(MVC.API.Device.ExportRetrieve(Model.ExportSessionId)) +, Tuple.Create(Tuple.Create("", 9304), Tuple.Create(Url.Action(MVC.API.Device.ExportRetrieve(Model.ExportSessionId)) #line default #line hidden -, 9314), false) +, 9304), false) ); WriteLiteral(" class=\"button\""); diff --git a/Disco.Web/Views/Device/Import.cshtml b/Disco.Web/Views/Device/Import.cshtml index bad1c8e7..2079fceb 100644 --- a/Disco.Web/Views/Device/Import.cshtml +++ b/Disco.Web/Views/Device/Import.cshtml @@ -55,10 +55,16 @@ }
-

CSV Import Specification

+

XLSX/CSV Import Specification

Format

    -
  • The import file must be in comma-separated values format (CSV Reference).
  • +
  • + The import file must be in either: +
      +
    • CSV (comma-separated values) format (CSV Reference), or
    • +
    • XLSX (Microsoft Excel) format
    • +
    +
  • Be conscious of editors removing leading zeros from serial numbers (ie: Microsoft Excel).

Fields

diff --git a/Disco.Web/Views/Device/Import.generated.cs b/Disco.Web/Views/Device/Import.generated.cs index a678ccae..a3584c9a 100644 --- a/Disco.Web/Views/Device/Import.generated.cs +++ b/Disco.Web/Views/Device/Import.generated.cs @@ -2,7 +2,7 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. -// Runtime Version:4.0.30319.34014 +// Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -27,7 +27,6 @@ namespace Disco.Web.Views.Device using System.Web.UI; using System.Web.WebPages; using Disco; - using Disco.BI.Extensions; using Disco.Models.Repository; #line 2 "..\..\Views\Device\Import.cshtml" @@ -211,17 +210,27 @@ WriteLiteral(" \r\n

CSV Import Specification

\r\n

Format

\r\n \r\n
  • The import file must be in comma-separated values fo" + -"rmat ( +

    XLSX/CSV Import Specification

    +

    Format

    +
      +
    • + The import file must be in either: +
        +
      • CSV (comma-separated values) format (CSV Reference).
      • \r\n
      • Be conscious of editors removing leadi" + -"ng zeros from serial numbers (ie: Microsoft Excel).
      • \r\n
      \r\n " + -"

      Fields

      \r\n CSV Reference), or
    • +
    • XLSX (Microsoft Excel) format
    • +
    +
  • +
  • Be conscious of editors removing leading zeros from serial numbers (ie: Microsoft Excel).
  • + +

    Fields

    + Field Name\r\n Description\r\n "\r\n \r\n \r\n"); - #line 74 "..\..\Views\Device\Import.cshtml" + #line 80 "..\..\Views\Device\Import.cshtml" #line default #line hidden - #line 74 "..\..\Views\Device\Import.cshtml" + #line 80 "..\..\Views\Device\Import.cshtml" foreach (var field in Model.HeaderTypes) { @@ -255,7 +264,7 @@ WriteLiteral(">Field Name\r\n Description\r\n WriteLiteral(" \r\n "); - #line 77 "..\..\Views\Device\Import.cshtml" + #line 83 "..\..\Views\Device\Import.cshtml" Write(field.Item2); @@ -266,7 +275,7 @@ WriteLiteral("\r\n \r\n"); WriteLiteral(" "); - #line 79 "..\..\Views\Device\Import.cshtml" + #line 85 "..\..\Views\Device\Import.cshtml" Write(field.Item3); @@ -275,13 +284,13 @@ WriteLiteral(" "); WriteLiteral("\r\n"); - #line 80 "..\..\Views\Device\Import.cshtml" + #line 86 "..\..\Views\Device\Import.cshtml" #line default #line hidden - #line 80 "..\..\Views\Device\Import.cshtml" + #line 86 "..\..\Views\Device\Import.cshtml" if (field.Item1 == DeviceImportFieldTypes.DeviceSerialNumber.ToString()) { @@ -291,7 +300,7 @@ WriteLiteral("\r\n"); WriteLiteral(" Required\r\n"); - #line 83 "..\..\Views\Device\Import.cshtml" + #line 89 "..\..\Views\Device\Import.cshtml" } else if (field.Item1 == DeviceImportFieldTypes.ModelId.ToString()) { @@ -308,7 +317,7 @@ WriteLiteral(" id=\"Devices_Import_Documentation_DeviceModels_Button\""); WriteLiteral(">Show IDs)\r\n"); - #line 87 "..\..\Views\Device\Import.cshtml" + #line 93 "..\..\Views\Device\Import.cshtml" } else if (field.Item1 == DeviceImportFieldTypes.ProfileId.ToString()) { @@ -325,7 +334,7 @@ WriteLiteral(" id=\"Devices_Import_Documentation_DeviceProfiles_Button\""); WriteLiteral(">Show IDs)\r\n"); - #line 91 "..\..\Views\Device\Import.cshtml" + #line 97 "..\..\Views\Device\Import.cshtml" } else if (field.Item1 == DeviceImportFieldTypes.BatchId.ToString()) { @@ -342,7 +351,7 @@ WriteLiteral(" id=\"Devices_Import_Documentation_DeviceBatches_Button\""); WriteLiteral(">Show IDs)\r\n"); - #line 95 "..\..\Views\Device\Import.cshtml" + #line 101 "..\..\Views\Device\Import.cshtml" } @@ -351,7 +360,7 @@ WriteLiteral(">Show IDs)\r\n"); WriteLiteral("\r\n \r\n \r\n"); - #line 99 "..\..\Views\Device\Import.cshtml" + #line 105 "..\..\Views\Device\Import.cshtml" } @@ -382,13 +391,13 @@ WriteLiteral(@"> "); - #line 115 "..\..\Views\Device\Import.cshtml" + #line 121 "..\..\Views\Device\Import.cshtml" #line default #line hidden - #line 115 "..\..\Views\Device\Import.cshtml" + #line 121 "..\..\Views\Device\Import.cshtml" foreach (var dm in Model.DeviceModels) { @@ -398,7 +407,7 @@ WriteLiteral(@"> WriteLiteral(" \r\n "); - #line 118 "..\..\Views\Device\Import.cshtml" + #line 124 "..\..\Views\Device\Import.cshtml" Write(Html.ActionLink(dm.Id.ToString(), MVC.Config.DeviceModel.Index(dm.Id))); @@ -407,7 +416,7 @@ WriteLiteral(" \r\n ") WriteLiteral("\r\n "); - #line 119 "..\..\Views\Device\Import.cshtml" + #line 125 "..\..\Views\Device\Import.cshtml" Write(dm.ToString()); @@ -416,7 +425,7 @@ WriteLiteral("\r\n "); WriteLiteral("\r\n "); - #line 120 "..\..\Views\Device\Import.cshtml" + #line 126 "..\..\Views\Device\Import.cshtml" Write(dm.Manufacturer); @@ -425,7 +434,7 @@ WriteLiteral("\r\n "); WriteLiteral("\r\n "); - #line 121 "..\..\Views\Device\Import.cshtml" + #line 127 "..\..\Views\Device\Import.cshtml" Write(dm.Model); @@ -434,7 +443,7 @@ WriteLiteral("\r\n "); WriteLiteral("\r\n \r\n"); - #line 123 "..\..\Views\Device\Import.cshtml" + #line 129 "..\..\Views\Device\Import.cshtml" } @@ -465,13 +474,13 @@ WriteLiteral(@"> "); - #line 139 "..\..\Views\Device\Import.cshtml" + #line 145 "..\..\Views\Device\Import.cshtml" #line default #line hidden - #line 139 "..\..\Views\Device\Import.cshtml" + #line 145 "..\..\Views\Device\Import.cshtml" foreach (var dp in Model.DeviceProfiles) { @@ -481,7 +490,7 @@ WriteLiteral(@"> WriteLiteral(" \r\n "); - #line 142 "..\..\Views\Device\Import.cshtml" + #line 148 "..\..\Views\Device\Import.cshtml" Write(Html.ActionLink(dp.Id.ToString(), MVC.Config.DeviceProfile.Index(dp.Id))); @@ -490,7 +499,7 @@ WriteLiteral(" \r\n ") WriteLiteral("\r\n "); - #line 143 "..\..\Views\Device\Import.cshtml" + #line 149 "..\..\Views\Device\Import.cshtml" Write(dp.Name); @@ -499,7 +508,7 @@ WriteLiteral("\r\n "); WriteLiteral("\r\n "); - #line 144 "..\..\Views\Device\Import.cshtml" + #line 150 "..\..\Views\Device\Import.cshtml" Write(dp.ShortName); @@ -508,7 +517,7 @@ WriteLiteral("\r\n "); WriteLiteral("\r\n "); - #line 145 "..\..\Views\Device\Import.cshtml" + #line 151 "..\..\Views\Device\Import.cshtml" Write(dp.Description); @@ -517,7 +526,7 @@ WriteLiteral("\r\n "); WriteLiteral("\r\n \r\n"); - #line 147 "..\..\Views\Device\Import.cshtml" + #line 153 "..\..\Views\Device\Import.cshtml" } @@ -547,13 +556,13 @@ WriteLiteral(@"> "); - #line 162 "..\..\Views\Device\Import.cshtml" + #line 168 "..\..\Views\Device\Import.cshtml" #line default #line hidden - #line 162 "..\..\Views\Device\Import.cshtml" + #line 168 "..\..\Views\Device\Import.cshtml" foreach (var db in Model.DeviceBatches) { @@ -563,7 +572,7 @@ WriteLiteral(@"> WriteLiteral(" \r\n "); - #line 165 "..\..\Views\Device\Import.cshtml" + #line 171 "..\..\Views\Device\Import.cshtml" Write(Html.ActionLink(db.Id.ToString(), MVC.Config.DeviceBatch.Index(db.Id))); @@ -572,7 +581,7 @@ WriteLiteral(" \r\n ") WriteLiteral("\r\n "); - #line 166 "..\..\Views\Device\Import.cshtml" + #line 172 "..\..\Views\Device\Import.cshtml" Write(db.Name); @@ -581,7 +590,7 @@ WriteLiteral("\r\n "); WriteLiteral("\r\n "); - #line 167 "..\..\Views\Device\Import.cshtml" + #line 173 "..\..\Views\Device\Import.cshtml" Write(CommonHelpers.FriendlyDate(db.PurchaseDate)); @@ -590,7 +599,7 @@ WriteLiteral("\r\n "); WriteLiteral("\r\n \r\n"); - #line 169 "..\..\Views\Device\Import.cshtml" + #line 175 "..\..\Views\Device\Import.cshtml" } @@ -620,7 +629,7 @@ WriteLiteral(" \r\n \r\n
    " \r\n \r\n\r\n"); - #line 210 "..\..\Views\Device\Import.cshtml" + #line 216 "..\..\Views\Device\Import.cshtml" if (Model.CompletedImportSessionContext != null) { @@ -642,7 +651,7 @@ WriteLiteral(" class=\"fa fa-lg fa-check\""); WriteLiteral(">Successfully imported/updated "); - #line 213 "..\..\Views\Device\Import.cshtml" + #line 219 "..\..\Views\Device\Import.cshtml" Write(Model.CompletedImportSessionContext.AffectedRecords); @@ -651,7 +660,7 @@ WriteLiteral(">Successfully imported/updated "); WriteLiteral(" device"); - #line 213 "..\..\Views\Device\Import.cshtml" + #line 219 "..\..\Views\Device\Import.cshtml" Write(Model.CompletedImportSessionContext.AffectedRecords != 1 ? "s" : null); @@ -660,7 +669,7 @@ WriteLiteral(" device"); WriteLiteral(".\r\n
    File: "); - #line 214 "..\..\Views\Device\Import.cshtml" + #line 220 "..\..\Views\Device\Import.cshtml" Write(Model.CompletedImportSessionContext.Filename); @@ -687,7 +696,7 @@ WriteLiteral(@" - -@* -
    - @if (Model.ImportDevices.Count > 0) - { -

    Parsed @Model.ImportDevices.Count Device Record@(Model.ImportDevices.Count != 1 ? "s" : null)

    -

    - @importDeviceOkCount of @Model.ImportDevices.Count Device@(Model.ImportDevices.Count != 1 ? "s" : null) are ready for import. -

    - if (importDeviceErrorCount > 0) - { -

    - @(importDeviceErrorCount) Record@(importDeviceErrorCount != 1 ? "s" : null) will be skipped if the import continues -

    - } - -
    -
      - @if (importDeviceNewCount > 0) - {
    • - -
    • }@if (importDeviceUpdateCount > 0) - {
    • - -
    • }@if (importDeviceErrorCount > 0) - {
    • - -
    • } -
    - -
    - - - - - - - - - - - - - - - - - @foreach (var device in Model.ImportDevices) - { - bool isUpdate = device.Device != null; - string error; - - - - - - - - - - - - } - -
    RowActionSerial NumberModelProfileBatchAssigned UserLocationAsset Number
    - @((Model.ImportDevices.IndexOf(device) + 1)) - - @(device.ImportStatus()) - - @if (device.Device == null) - { - @device.SerialNumber - } - else - { - @Html.ActionLink(device.SerialNumber, MVC.Device.Show(device.SerialNumber), new { target = "_blank" }) - } - - @if (device.Errors.TryGetValue("SerialNumber", out error)) - { -
    @error
    - } -
    - @if (device.Errors.TryGetValue("DeviceModelId", out error)) - { -
    @error
    - } - else - { - if (!isUpdate || device.DeviceModelId != device.Device.DeviceModelId) - { - Model Image - @device.DeviceModel.ToString() - } - else - { - No Change - } - }
    - @if (device.Errors.TryGetValue("DeviceProfileId", out error)) - { -
    @error
    - } - else - { - if (!isUpdate || device.DeviceProfileId != device.Device.DeviceProfileId) - { - @device.DeviceProfile.ToString() - } - else - { - No Change - } - }
    - @if (device.Errors.TryGetValue("DeviceBatchId", out error)) - { -
    @error
    - } - else - { - if (!isUpdate || device.DeviceBatchId != device.Device.DeviceBatchId) - { - if (device.DeviceBatch == null) - { - <None> - } - else - { - @device.DeviceBatch.ToString() - } - } - else - { - No Change - } - }
    - @if (device.Errors.TryGetValue("AssignedUserId", out error)) - { -
    @error
    - } - else - { - if (!isUpdate || device.AssignedUserId != device.Device.AssignedUserId) - { - if (device.AssignedUser == null) - { - <None> - } - else - { - @device.AssignedUser.ToString() - } - } - else - { - No Change - } - }
    - @if (device.Errors.TryGetValue("Location", out error)) - { -
    @error
    - } - else - { - if (!isUpdate || device.Location != device.Device.Location) - { - if (device.Location == null) - { - <None> - } - else - { - @device.Location - } - } - else - { - No Change - } - }
    - @if (device.Errors.TryGetValue("AssetNumber", out error)) - { -
    @error
    - } - else - { - if (!isUpdate || device.AssetNumber != device.Device.AssetNumber) - { - if (device.AssetNumber == null) - { - <None> - } - else - { - @device.AssetNumber - } - } - else - { - No Change - } - }
    - - if (importDeviceOkCount > 0) - { -
    - @Html.ActionLinkButton(string.Format("Import {0} Device{1}", importDeviceOkCount, importDeviceOkCount != 1 ? "s" : null), MVC.API.Device.ImportProcess(Model.ImportParseTaskId)) -
    - } - } - else - { -

    No Devices were found in this file

    - } -
    *@ diff --git a/Disco.Web/Views/Device/ImportReview.generated.cs b/Disco.Web/Views/Device/ImportReview.generated.cs index 568f745f..413238c4 100644 --- a/Disco.Web/Views/Device/ImportReview.generated.cs +++ b/Disco.Web/Views/Device/ImportReview.generated.cs @@ -2,7 +2,7 @@ //------------------------------------------------------------------------------ // // This code was generated by a tool. -// Runtime Version:4.0.30319.34014 +// Runtime Version:4.0.30319.42000 // // Changes to this file may cause incorrect behavior and will be lost if // the code is regenerated. @@ -33,7 +33,6 @@ namespace Disco.Web.Views.Device using System.Web.UI; using System.Web.WebPages; using Disco; - using Disco.BI.Extensions; using Disco.Models.Repository; #line 2 "..\..\Views\Device\ImportReview.cshtml" @@ -377,7 +376,7 @@ WriteLiteral(">\r\n \r\n \r\n #line hidden #line 67 "..\..\Views\Device\ImportReview.cshtml" - foreach (var header in Model.Context.ParsedHeaders) + foreach (var header in Model.Context.Columns.Where(c => c.Type != DeviceImportFieldTypes.IgnoreColumn)) { @@ -387,7 +386,7 @@ WriteLiteral(" "); #line 69 "..\..\Views\Device\ImportReview.cshtml" - Write(Model.HeaderTypes.FirstOrDefault(h => h.Item1 == header.Item2).Item2); + Write(Model.HeaderTypes.FirstOrDefault(h => h.Item1 == header.Type).Item2); #line default @@ -412,7 +411,7 @@ WriteLiteral(" \r\n \r\n #line hidden #line 75 "..\..\Views\Device\ImportReview.cshtml" - foreach (var header in Model.Context.ParsedHeaders) + foreach (var header in Model.Context.Columns.Where(c => c.Type != DeviceImportFieldTypes.IgnoreColumn)) { @@ -422,7 +421,7 @@ WriteLiteral(" "); #line 77 "..\..\Views\Device\ImportReview.cshtml" - Write(header.Item1); + Write(header.Name); #line default @@ -455,15 +454,15 @@ WriteLiteral(" \r\n \r\n
    \r\n\r\n\r\n\r\n"); - -WriteLiteral("\r\n"); +" });\r\n\r\n });\r\n\r\n"); } }