diff --git a/.github/workflows/mainworkflow.yml b/.github/workflows/mainworkflow.yml index c8c3466..ba28f55 100644 --- a/.github/workflows/mainworkflow.yml +++ b/.github/workflows/mainworkflow.yml @@ -20,7 +20,7 @@ jobs: - name: Setup .NET uses: actions/setup-dotnet@v4 with: - dotnet-version: 8.0.x + dotnet-version: 9.0.x - name: Restore dependencies run: dotnet restore - name: Build diff --git a/JMayer.Example.WebAssemblyBlazor.Shared/Data/Assets/Asset.cs b/JMayer.Example.WebAssemblyBlazor.Shared/Data/Assets/Asset.cs index e46a56f..888bd06 100644 --- a/JMayer.Example.WebAssemblyBlazor.Shared/Data/Assets/Asset.cs +++ b/JMayer.Example.WebAssemblyBlazor.Shared/Data/Assets/Asset.cs @@ -1,11 +1,12 @@ using JMayer.Data.Data; +using System.ComponentModel.DataAnnotations; namespace JMayer.Example.WebAssemblyBlazor.Shared.Data.Assets; /// /// The class represents an asset (equipment) that needs to be monitored and work orders can be preformed on it. /// -public class Asset : UserEditableDataObject +public class Asset : DataObject { /// /// The property gets/sets the common category for the asset. @@ -38,16 +39,18 @@ public class Asset : UserEditableDataObject /// /// This is only used by the backend when updating the parent path. /// - internal string MeAsParentPath - { - get => ParentID == null ? Name : $"{ParentPath}/{Name}"; - } + internal string MeAsParentPath => ParentID is null ? (Name ?? string.Empty) : $"{ParentPath}/{Name ?? string.Empty}"; /// /// The property gets/sets the model for the asset. /// public string? Model { get; set; } + /// + /// Overridden to add Required data annotation. + [Required] + public override string? Name { get => base.Name; set => base.Name = value; } + /// /// The property gets/sets id for the parent of this asset. /// diff --git a/JMayer.Example.WebAssemblyBlazor.Shared/Data/Assets/StorageLocation.cs b/JMayer.Example.WebAssemblyBlazor.Shared/Data/Assets/StorageLocation.cs index ce73e3d..74e2c33 100644 --- a/JMayer.Example.WebAssemblyBlazor.Shared/Data/Assets/StorageLocation.cs +++ b/JMayer.Example.WebAssemblyBlazor.Shared/Data/Assets/StorageLocation.cs @@ -7,17 +7,14 @@ namespace JMayer.Example.WebAssemblyBlazor.Shared.Data.Assets; /// The class represents a storage location for an area asset. /// /// -/// The OwnerId in the SubUserEditableDataObject will represent an asset. +/// The OwnerId in the SubDataObject will represent an asset. /// -public class StorageLocation : SubUserEditableDataObject +public class StorageLocation : SubDataObject { /// /// The property gets/sets the friendly name (locations concatenated). /// - public string FriendlyName - { - get => $"{LocationA}{(!string.IsNullOrWhiteSpace(LocationB) ? $" {LocationB}" : string.Empty)}{(!string.IsNullOrWhiteSpace(LocationC) ? $" {LocationC}" : string.Empty)}"; - } + public string FriendlyName => $"{LocationA}{(string.IsNullOrWhiteSpace(LocationB) is false ? $" {LocationB}" : string.Empty)}{(string.IsNullOrWhiteSpace(LocationC) is false ? $" {LocationC}" : string.Empty)}"; /// /// The property gets/sets the name of the A location for the storage location. @@ -46,7 +43,7 @@ public override void MapProperties(DataObject dataObject) { base.MapProperties(dataObject); - if (dataObject is StorageLocation storageLocation) + if (dataObject is StorageLocation storageLocation) { LocationA = storageLocation.LocationA; LocationB = storageLocation.LocationB; diff --git a/JMayer.Example.WebAssemblyBlazor.Shared/Data/Parts/Part.cs b/JMayer.Example.WebAssemblyBlazor.Shared/Data/Parts/Part.cs index 7d482b0..8ea25cb 100644 --- a/JMayer.Example.WebAssemblyBlazor.Shared/Data/Parts/Part.cs +++ b/JMayer.Example.WebAssemblyBlazor.Shared/Data/Parts/Part.cs @@ -1,11 +1,12 @@ using JMayer.Data.Data; +using System.ComponentModel.DataAnnotations; namespace JMayer.Example.WebAssemblyBlazor.Shared.Data.Parts; /// /// The class represents a part to be used for repairing an asset. /// -public class Part : UserEditableDataObject +public class Part : DataObject { /// /// The property gets/sets the common category for the part. @@ -32,6 +33,11 @@ public class Part : UserEditableDataObject /// public string? Model { get; set; } + /// + /// Overridden to add Required data annotation. + [Required] + public override string? Name { get => base.Name; set => base.Name = value; } + /// /// The property gets/sets is no longer procedured by the manfacturer. /// diff --git a/JMayer.Example.WebAssemblyBlazor.Shared/Data/Parts/Stock.cs b/JMayer.Example.WebAssemblyBlazor.Shared/Data/Parts/Stock.cs index dec2866..7c7641c 100644 --- a/JMayer.Example.WebAssemblyBlazor.Shared/Data/Parts/Stock.cs +++ b/JMayer.Example.WebAssemblyBlazor.Shared/Data/Parts/Stock.cs @@ -7,9 +7,9 @@ namespace JMayer.Example.WebAssemblyBlazor.Shared.Data.Parts; /// The class represents stock for a part at a particular storage location. /// /// -/// The OwnerId in the SubUserEditableDataObject will represent a part. +/// The OwnerId in the SubDataObject will represent a part. /// -public class Stock : SubUserEditableDataObject +public class Stock : SubDataObject { /// /// The property gets/sets the amount of stock at the location for the part. @@ -22,6 +22,7 @@ public class Stock : SubUserEditableDataObject /// The property gets/sets the id for the storage location the part is stored at. /// [Required] + [Range(1, long.MaxValue, ErrorMessage = "The Storage Location field is required.")] public long StorageLocationID { get; set; } /// diff --git a/JMayer.Example.WebAssemblyBlazor.Shared/Database/BHSExampleBuilder.cs b/JMayer.Example.WebAssemblyBlazor.Shared/Database/BHSExampleBuilder.cs index aba1926..5514758 100644 --- a/JMayer.Example.WebAssemblyBlazor.Shared/Database/BHSExampleBuilder.cs +++ b/JMayer.Example.WebAssemblyBlazor.Shared/Database/BHSExampleBuilder.cs @@ -16,26 +16,86 @@ public class BHSExampleBuilder /// public IAssetDataLayer AssetDataLayer { get; set; } + /// + /// The constant for the ATR category name. + /// + private const string AtrCategoryName = "ATR"; + + /// + /// The constant for the bag room category name. + /// + private const string BagRoomCategoryName = "Bag Room"; + + /// + /// The constant for the belt category name. + /// + private const string BeltCategoryName = "Belt"; + + /// + /// The constant for the conveyor category name. + /// + private const string ConveyorCategoryName = "Conveyor"; + + /// + /// The constant for the diverter category name. + /// + private const string DiverterCategoryName = "Diverter"; + + /// + /// The constant for the electrical contact category name. + /// + private const string ElectricalContactCategoryName = "Electrical Contact"; + /// /// The constant for the main part storage area asset name. /// public const string MainPartStorageAreaAssetName = "Main Part Storage"; + /// + /// The constant for the conveyor length for the main sortation subsystem. + /// + private const int MainSortationConveyorLength = 15; + + /// + /// The constant for the conveyor length for the makeup unit subsystem. + /// + private const int MakeupUnitConveyorLength = 6; + + /// + /// The constant for the motor category name. + /// + private const string MotorCategoryName = "Motor"; + /// /// The property gets/sets the data layer used to interact with parts. /// public IPartDataLayer PartDataLayer { get; set; } + /// + /// The constant for the photoeye category name. + /// + private const string PhotoeyeCategoryName = "Photoeye"; + /// /// The property gets/sets the data layer used to interact with part stock. /// public IStockDataLayer StockDataLayer { get; set; } + /// + /// The constant for the storage category name. + /// + private const string StorageCategoryName = "Storage"; + /// /// The property gets/sets the data layer used to interact with asset storage locations. /// public IStorageLocationDataLayer StorageLocationDataLayer { get; set; } + /// + /// The constant for the subsystem category name. + /// + private const string SubSystemCategoryName = "SubSystem"; + /// /// The default constructor. /// @@ -65,15 +125,15 @@ private void BuildAssets() { Asset bagRoomAsset = AssetDataLayer.CreateAsync(new Asset() { - Category = "Bag Room", + Category = BagRoomCategoryName, Description = "The main bag room for the BHS.", - Name = "Main Bag Room", + Name = $"Main Bag Room", Type = AssetType.Group, }).Result; Asset asset = AssetDataLayer.CreateAsync(new Asset() { - Category = "SubSystem", + Category = SubSystemCategoryName, Description = "The main sortation line for the bag room.", Name = "MS1", ParentID = bagRoomAsset.Integer64ID, @@ -82,7 +142,7 @@ private void BuildAssets() _ = AssetDataLayer.CreateAsync(new Asset() { - Category = "ATR", + Category = AtrCategoryName, Description = "The scanner array for reading bags on the main sortation line.", Name = "MS1-ATR", ParentID = asset.Integer64ID, @@ -90,11 +150,11 @@ private void BuildAssets() Type = AssetType.Equipment, }); - for (int index = 1; index <= 15; index++) + for (int index = 1; index <= MainSortationConveyorLength; index++) { _ = AssetDataLayer.CreateAsync(new Asset() { - Category = "Conveyor", + Category = ConveyorCategoryName, Description = "The conveyor which makes up the main sortation line.", Name = $"MS1-{index:00}", ParentID = asset.Integer64ID, @@ -105,7 +165,7 @@ private void BuildAssets() asset = AssetDataLayer.CreateAsync(new Asset() { - Category = "SubSystem", + Category = SubSystemCategoryName, Description = "The one of the destination lines for the bag room.", Name = "MU1", ParentID = bagRoomAsset.Integer64ID, @@ -114,7 +174,7 @@ private void BuildAssets() _ = AssetDataLayer.CreateAsync(new Asset() { - Category = "Diverter", + Category = DiverterCategoryName, Description = "The diverter which pushes bags onto the MU1 destination line.", Name = "MU1-DIV", ParentID = asset.Integer64ID, @@ -122,11 +182,11 @@ private void BuildAssets() Type = AssetType.Equipment, }); - for (int index = 1; index <= 6; index++) + for (int index = 1; index <= MakeupUnitConveyorLength; index++) { _ = AssetDataLayer.CreateAsync(new Asset() { - Category = "Conveyor", + Category = ConveyorCategoryName, Description = "The conveyor which makes up the MU1 destination line.", Name = $"MU1-{index:00}", ParentID = asset.Integer64ID, @@ -137,7 +197,7 @@ private void BuildAssets() asset = AssetDataLayer.CreateAsync(new Asset() { - Category = "SubSystem", + Category = SubSystemCategoryName, Description = "The one of the destination lines for the bag room.", Name = "MU2", ParentID = bagRoomAsset.Integer64ID, @@ -146,7 +206,7 @@ private void BuildAssets() _ = AssetDataLayer.CreateAsync(new Asset() { - Category = "Diverter", + Category = DiverterCategoryName, Description = "The diverter which pushes bags onto the MU2 destination line.", Name = "MU2-DIV", ParentID = asset.Integer64ID, @@ -154,11 +214,11 @@ private void BuildAssets() Type = AssetType.Equipment, }); - for (int index = 1; index <= 6; index++) + for (int index = 1; index <= MakeupUnitConveyorLength; index++) { _ = AssetDataLayer.CreateAsync(new Asset() { - Category = "Conveyor", + Category = ConveyorCategoryName, Description = "The conveyor which makes up the MU2 destination line.", Name = $"MU2-{index:00}", ParentID = asset.Integer64ID, @@ -169,18 +229,18 @@ private void BuildAssets() asset = AssetDataLayer.CreateAsync(new Asset() { - Category = "SubSystem", + Category = SubSystemCategoryName, Description = "The runout line for the bag room.", Name = "MU3", ParentID = bagRoomAsset.Integer64ID, Type = AssetType.Group, }).Result; - for (int index = 1; index <= 6; index++) + for (int index = 1; index <= MakeupUnitConveyorLength; index++) { _ = AssetDataLayer.CreateAsync(new Asset() { - Category = "Conveyor", + Category = ConveyorCategoryName, Description = "The conveyor which makes up the MU3 destination line.", Name = $"MU3-{index:00}", ParentID = asset.Integer64ID, @@ -197,22 +257,22 @@ private void BuildParts() { _ = PartDataLayer.CreateAsync(new Part() { - Category = "Belt", + Category = BeltCategoryName, Name = "Power Turn Belt", }); _ = PartDataLayer.CreateAsync(new Part() { - Category = "Contact", + Category = ElectricalContactCategoryName, Name = "Motor Contactor", }); _ = PartDataLayer.CreateAsync(new Part() { - Category = "Motor", + Category = MotorCategoryName, Name = "Motor 1HP Drive", }); _ = PartDataLayer.CreateAsync(new Part() { - Category = "Photoeye", + Category = PhotoeyeCategoryName, Name = "Photoeye, Polarized Retro Reflective", }); } @@ -232,7 +292,6 @@ private void BuildStock() _ = StockDataLayer.CreateAsync(new Stock() { Amount = 5 * (index + 1), - Name = "A Name", OwnerInteger64ID = parts[index].Integer64ID, StorageLocationID = storageLocations[index].Integer64ID, StorageLocationName = storageLocations[index].FriendlyName, @@ -248,13 +307,13 @@ private void BuildStorageLocations() { Asset asset = AssetDataLayer.CreateAsync(new Asset() { - Category = "Storage", + Category = StorageCategoryName, Description = "The main part storage for the BHS.", Name = MainPartStorageAreaAssetName, Type = AssetType.Area, }).Result; - StorageLocation storageLocation = new StorageLocation() + StorageLocation storageLocation = new() { LocationA = "A1", LocationB = "R1", diff --git a/JMayer.Example.WebAssemblyBlazor.Shared/Database/DataLayer/Assets/AssetDataLayer.cs b/JMayer.Example.WebAssemblyBlazor.Shared/Database/DataLayer/Assets/AssetDataLayer.cs index bdfa594..c35f0e1 100644 --- a/JMayer.Example.WebAssemblyBlazor.Shared/Database/DataLayer/Assets/AssetDataLayer.cs +++ b/JMayer.Example.WebAssemblyBlazor.Shared/Database/DataLayer/Assets/AssetDataLayer.cs @@ -6,23 +6,28 @@ namespace JMayer.Example.WebAssemblyBlazor.Shared.Database.DataLayer.Assets; /// /// The class manages CRUD interactions with the database for an asset. /// -public class AssetDataLayer : UserEditableDataLayer, IAssetDataLayer +public class AssetDataLayer : StandardCRUDDataLayer, IAssetDataLayer { + /// + /// The default constructor. + /// + public AssetDataLayer() + { + IsOldDataObjectDetectionEnabled = true; + IsUniqueNameRequired = true; + } + /// /// /// This is overriden so the parent path can be set before creation. /// public override async Task CreateAsync(Asset dataObject, CancellationToken cancellationToken = default) { - //Set the parent path is a parent exists. - if (dataObject.ParentID != null) + //Set the parent path if a parent exists. + if (dataObject.ParentID is not null) { Asset? parent = await GetSingleAsync(obj => obj.Integer64ID == dataObject.ParentID, cancellationToken); - - if (parent != null) - { - dataObject.ParentPath = parent.MeAsParentPath; - } + dataObject.ParentPath = parent?.MeAsParentPath; } return await base.CreateAsync(dataObject, cancellationToken); @@ -53,23 +58,24 @@ public override async Task DeleteAsync(Asset dataObject, CancellationToken cance /// A list of children assets or an empty list is none exists. private async Task> GetChildrenAsync(Asset parent, CancellationToken cancellationToken) { - List returnList = []; List children = await GetAllAsync(obj => obj.ParentID == parent.Integer64ID, cancellationToken: cancellationToken); - if (children.Count > 0) + if (children.Count is 0) { - returnList = [.. children]; + return []; + } - foreach (Asset child in children) + List returnList = [.. children]; + + foreach (Asset child in children) + { + if (child.ParentID is not null) { - if (child.ParentID != null) - { - List temp = await GetChildrenAsync(child, cancellationToken); + List temp = await GetChildrenAsync(child, cancellationToken); - if (temp.Count > 0) - { - returnList.AddRange(temp); - } + if (temp.Count > 0) + { + returnList.AddRange(temp); } } } @@ -87,9 +93,9 @@ public override async Task UpdateAsync(Asset dataObject, CancellationToke Asset? originalDataObject = await GetSingleAsync(obj => obj.Integer64ID == dataObject.Integer64ID, cancellationToken); //Update the parent path if the parent has changed. - if (originalDataObject != null && originalDataObject.ParentID != dataObject.ParentID) + if (originalDataObject is not null && originalDataObject.ParentID != dataObject.ParentID) { - if (dataObject.ParentID == null) + if (dataObject.ParentID is null) { dataObject.ParentPath = null; } @@ -103,7 +109,7 @@ public override async Task UpdateAsync(Asset dataObject, CancellationToke dataObject = await base.UpdateAsync(dataObject, cancellationToken); //All child under the asset must update their parent's path if the name has changed or the parent has changed. - if (originalDataObject != null && (originalDataObject.Name != dataObject.Name || originalDataObject.ParentID != dataObject.ParentID)) + if (originalDataObject is not null && (originalDataObject.Name != dataObject.Name || originalDataObject.ParentID != dataObject.ParentID)) { await UpdateParentPathAsync(dataObject.MeAsParentPath, dataObject, cancellationToken); } @@ -122,15 +128,17 @@ private async Task UpdateParentPathAsync(string? parentPath, Asset parentAsset, { List children = await GetAllAsync(obj => obj.ParentID == parentAsset.Integer64ID, cancellationToken: cancellationToken); - if (children.Count > 0) + if (children.Count is 0) { - foreach (Asset childAsset in children) - { - childAsset.ParentPath = parentPath; - await UpdateParentPathAsync(childAsset.MeAsParentPath, childAsset, cancellationToken); - } + return; + } - _ = await UpdateAsync(children, cancellationToken); + foreach (Asset childAsset in children) + { + childAsset.ParentPath = parentPath; + await UpdateParentPathAsync(childAsset.MeAsParentPath, childAsset, cancellationToken); } + + _ = await UpdateAsync(children, cancellationToken); } } diff --git a/JMayer.Example.WebAssemblyBlazor.Shared/Database/DataLayer/Assets/IAssetDataLayer.cs b/JMayer.Example.WebAssemblyBlazor.Shared/Database/DataLayer/Assets/IAssetDataLayer.cs index 3a64f8b..3b837a0 100644 --- a/JMayer.Example.WebAssemblyBlazor.Shared/Database/DataLayer/Assets/IAssetDataLayer.cs +++ b/JMayer.Example.WebAssemblyBlazor.Shared/Database/DataLayer/Assets/IAssetDataLayer.cs @@ -6,6 +6,6 @@ namespace JMayer.Example.WebAssemblyBlazor.Shared.Database.DataLayer.Assets; /// /// The interface for interacting with an asset collection in a database using CRUD operations. /// -public interface IAssetDataLayer : IUserEditableDataLayer +public interface IAssetDataLayer : IStandardCRUDDataLayer { } diff --git a/JMayer.Example.WebAssemblyBlazor.Shared/Database/DataLayer/Assets/IStorageLocationDataLayer.cs b/JMayer.Example.WebAssemblyBlazor.Shared/Database/DataLayer/Assets/IStorageLocationDataLayer.cs index f7549e0..96d1330 100644 --- a/JMayer.Example.WebAssemblyBlazor.Shared/Database/DataLayer/Assets/IStorageLocationDataLayer.cs +++ b/JMayer.Example.WebAssemblyBlazor.Shared/Database/DataLayer/Assets/IStorageLocationDataLayer.cs @@ -6,6 +6,6 @@ namespace JMayer.Example.WebAssemblyBlazor.Shared.Database.DataLayer.Assets; /// /// The interface for interacting with a storage location collection in a database using CRUD operations. /// -public interface IStorageLocationDataLayer : IUserEditableDataLayer +public interface IStorageLocationDataLayer : IStandardSubCRUDDataLayer { } diff --git a/JMayer.Example.WebAssemblyBlazor.Shared/Database/DataLayer/Assets/StorageLocationDataLayer.cs b/JMayer.Example.WebAssemblyBlazor.Shared/Database/DataLayer/Assets/StorageLocationDataLayer.cs index 81f22bb..8c4daae 100644 --- a/JMayer.Example.WebAssemblyBlazor.Shared/Database/DataLayer/Assets/StorageLocationDataLayer.cs +++ b/JMayer.Example.WebAssemblyBlazor.Shared/Database/DataLayer/Assets/StorageLocationDataLayer.cs @@ -8,7 +8,7 @@ namespace JMayer.Example.WebAssemblyBlazor.Shared.Database.DataLayer.Assets; /// /// The class manages CRUD interactions with the database for a storage location. /// -public class StorageLocationDataLayer : UserEditableDataLayer, IStorageLocationDataLayer +public class StorageLocationDataLayer : StandardSubCRUDDataLayer, IStorageLocationDataLayer { /// /// The data layer for interacting with assets. @@ -25,6 +25,8 @@ public class StorageLocationDataLayer : UserEditableDataLayer, /// The data layer for interacting with assets. public StorageLocationDataLayer(IAssetDataLayer assetDataLayer) { + IsOldDataObjectDetectionEnabled = true; + _assetDataLayer = assetDataLayer; _assetDataLayer.Deleted += AssetDataLayer_Deleted; } @@ -49,21 +51,18 @@ private async void AssetDataLayer_Deleted(object? sender, DeletedEventArgs e) /// /// - /// Overriding and not calling the base because the parent class forces the Name property - /// to be unique but the property is not used. Also, added additional server-side validation - /// unique to the storage location. + /// Overriding to check if the owner asset exists and the storage location doesn't already exist. /// public override async Task> ValidateAsync(StorageLocation dataObject, CancellationToken cancellationToken = default) { - ArgumentNullException.ThrowIfNull(dataObject); - List validationResults = dataObject.Validate(); + List validationResults = await base.ValidateAsync(dataObject, cancellationToken); - if (await _assetDataLayer.ExistAsync(obj => obj.Integer64ID == dataObject.OwnerInteger64ID, cancellationToken) == false) + if (await _assetDataLayer.ExistAsync(obj => obj.Integer64ID == dataObject.OwnerInteger64ID, cancellationToken) is false) { validationResults.Add(new ValidationResult($"The {dataObject.OwnerInteger64ID} asset was not found in the data store.", [nameof(StorageLocation.OwnerInteger64ID)])); } - if (await ExistAsync(obj => obj.Integer64ID != dataObject.Integer64ID && obj.LocationA == dataObject.LocationA && obj.LocationB == dataObject.LocationB && obj.LocationC == dataObject.LocationC, cancellationToken) == true) + if (await ExistAsync(obj => obj.Integer64ID != dataObject.Integer64ID && obj.LocationA == dataObject.LocationA && obj.LocationB == dataObject.LocationB && obj.LocationC == dataObject.LocationC, cancellationToken) is true) { validationResults.Add(new ValidationResult("The location already exists in the data store.", [nameof(StorageLocation.LocationA)])); } diff --git a/JMayer.Example.WebAssemblyBlazor.Shared/Database/DataLayer/Parts/IPartDataLayer.cs b/JMayer.Example.WebAssemblyBlazor.Shared/Database/DataLayer/Parts/IPartDataLayer.cs index cc4e32c..8df0a0b 100644 --- a/JMayer.Example.WebAssemblyBlazor.Shared/Database/DataLayer/Parts/IPartDataLayer.cs +++ b/JMayer.Example.WebAssemblyBlazor.Shared/Database/DataLayer/Parts/IPartDataLayer.cs @@ -6,6 +6,6 @@ namespace JMayer.Example.WebAssemblyBlazor.Shared.Database.DataLayer.Parts; /// /// The interface for interacting with a parts collection in a database using CRUD operations. /// -public interface IPartDataLayer : IUserEditableDataLayer +public interface IPartDataLayer : IStandardCRUDDataLayer { } diff --git a/JMayer.Example.WebAssemblyBlazor.Shared/Database/DataLayer/Parts/IStockDataLayer.cs b/JMayer.Example.WebAssemblyBlazor.Shared/Database/DataLayer/Parts/IStockDataLayer.cs index e830512..948b3ab 100644 --- a/JMayer.Example.WebAssemblyBlazor.Shared/Database/DataLayer/Parts/IStockDataLayer.cs +++ b/JMayer.Example.WebAssemblyBlazor.Shared/Database/DataLayer/Parts/IStockDataLayer.cs @@ -6,6 +6,6 @@ namespace JMayer.Example.WebAssemblyBlazor.Shared.Database.DataLayer.Parts; /// /// The interface for interacting with a part stock collection in a database using CRUD operations. /// -public interface IStockDataLayer : IUserEditableDataLayer +public interface IStockDataLayer : IStandardSubCRUDDataLayer { } diff --git a/JMayer.Example.WebAssemblyBlazor.Shared/Database/DataLayer/Parts/PartDataLayer.cs b/JMayer.Example.WebAssemblyBlazor.Shared/Database/DataLayer/Parts/PartDataLayer.cs index 0d6abe8..88014ea 100644 --- a/JMayer.Example.WebAssemblyBlazor.Shared/Database/DataLayer/Parts/PartDataLayer.cs +++ b/JMayer.Example.WebAssemblyBlazor.Shared/Database/DataLayer/Parts/PartDataLayer.cs @@ -6,6 +6,14 @@ namespace JMayer.Example.WebAssemblyBlazor.Shared.Database.DataLayer.Parts; /// /// The class manages CRUD interactions with the database for a part. /// -public class PartDataLayer : UserEditableDataLayer, IPartDataLayer +public class PartDataLayer : StandardCRUDDataLayer, IPartDataLayer { + /// + /// The default constructor. + /// + public PartDataLayer() + { + IsOldDataObjectDetectionEnabled = true; + IsUniqueNameRequired = true; + } } diff --git a/JMayer.Example.WebAssemblyBlazor.Shared/Database/DataLayer/Parts/StockDataLayer.cs b/JMayer.Example.WebAssemblyBlazor.Shared/Database/DataLayer/Parts/StockDataLayer.cs index 847c8b6..f934d54 100644 --- a/JMayer.Example.WebAssemblyBlazor.Shared/Database/DataLayer/Parts/StockDataLayer.cs +++ b/JMayer.Example.WebAssemblyBlazor.Shared/Database/DataLayer/Parts/StockDataLayer.cs @@ -9,7 +9,7 @@ namespace JMayer.Example.WebAssemblyBlazor.Shared.Database.DataLayer.Parts; /// /// The class manages CRUD interactions with the database for part stock. /// -public class StockDataLayer : UserEditableDataLayer, IStockDataLayer +public class StockDataLayer : StandardSubCRUDDataLayer, IStockDataLayer { /// /// The data layer for interacting with parts. @@ -36,6 +36,8 @@ public class StockDataLayer : UserEditableDataLayer, IStockDataLayer /// public StockDataLayer(IPartDataLayer partDataLayer, IStorageLocationDataLayer storageLocationDataLayer) { + IsOldDataObjectDetectionEnabled = true; + _partDataLayer = partDataLayer; _partDataLayer.Deleted += PartDataLayer_Deleted; @@ -112,26 +114,23 @@ private async void StorageLocationDataLayer_Updated(object? sender, JMayer.Data. /// /// - /// Overriding and not calling the base because the parent class forces the Name property - /// to be unique but the property is not used. Also, added additional server-side validation - /// unique to the stock. + /// Overriding to check if the owner part exists, the storage location exists and the stock location doesn't already exist. /// public override async Task> ValidateAsync(Stock dataObject, CancellationToken cancellationToken = default) { - ArgumentNullException.ThrowIfNull(dataObject); - List validationResults = dataObject.Validate(); + List validationResults = await base.ValidateAsync(dataObject, cancellationToken); - if (await _partDataLayer.ExistAsync(obj => obj.Integer64ID == dataObject.OwnerInteger64ID, cancellationToken) == false) + if (await _partDataLayer.ExistAsync(obj => obj.Integer64ID == dataObject.OwnerInteger64ID, cancellationToken) is false) { validationResults.Add(new ValidationResult($"The {dataObject.OwnerInteger64ID} part was not found in the data store.", [nameof(Stock.OwnerInteger64ID)])); } - if (await _storageLocationDataLayer.ExistAsync(obj => obj.Integer64ID == dataObject.StorageLocationID, cancellationToken) == false) + if (await _storageLocationDataLayer.ExistAsync(obj => obj.Integer64ID == dataObject.StorageLocationID, cancellationToken) is false) { validationResults.Add(new ValidationResult($"The {dataObject.StorageLocationID} storage location was not found in the data store.", [nameof(Stock.StorageLocationID)])); } - if (await ExistAsync(obj => obj.Integer64ID != dataObject.Integer64ID && obj.OwnerInteger64ID == dataObject.OwnerInteger64ID && obj.StorageLocationID == dataObject.StorageLocationID, cancellationToken) == true) + if (await ExistAsync(obj => obj.Integer64ID != dataObject.Integer64ID && obj.OwnerInteger64ID == dataObject.OwnerInteger64ID && obj.StorageLocationID == dataObject.StorageLocationID, cancellationToken) is true) { validationResults.Add(new ValidationResult("The stock location already exists in the data store for the part.", [nameof(Stock.StorageLocationID)])); } diff --git a/JMayer.Example.WebAssemblyBlazor.Shared/HTTP/DataLayer/Assets/AssetDataLayer.cs b/JMayer.Example.WebAssemblyBlazor.Shared/HTTP/DataLayer/Assets/AssetDataLayer.cs index 68c9a41..254217a 100644 --- a/JMayer.Example.WebAssemblyBlazor.Shared/HTTP/DataLayer/Assets/AssetDataLayer.cs +++ b/JMayer.Example.WebAssemblyBlazor.Shared/HTTP/DataLayer/Assets/AssetDataLayer.cs @@ -8,7 +8,7 @@ namespace JMayer.Example.WebAssemblyBlazor.Shared.HTTP.DataLayer.Assets; /// /// The class manages CRUD interactions with a remote server for an asset. /// -public class AssetDataLayer : UserEditableDataLayer, IAssetDataLayer +public class AssetDataLayer : StandardCRUDDataLayer, IAssetDataLayer { /// public AssetDataLayer(HttpClient httpClient) : base(httpClient) { } @@ -19,7 +19,7 @@ public AssetDataLayer(HttpClient httpClient) : base(httpClient) { } List? categories = null; HttpResponseMessage httpResponseMessage = await HttpClient.GetAsync($"api/{TypeName}/Category/All", cancellationToken); - if (httpResponseMessage.IsSuccessStatusCode && httpResponseMessage.StatusCode != HttpStatusCode.NoContent) + if (httpResponseMessage.IsSuccessStatusCode && httpResponseMessage.StatusCode is not HttpStatusCode.NoContent) { categories = await httpResponseMessage.Content.ReadFromJsonAsync>(cancellationToken); } diff --git a/JMayer.Example.WebAssemblyBlazor.Shared/HTTP/DataLayer/Assets/IAssetDataLayer.cs b/JMayer.Example.WebAssemblyBlazor.Shared/HTTP/DataLayer/Assets/IAssetDataLayer.cs index 1838ba1..3a18db6 100644 --- a/JMayer.Example.WebAssemblyBlazor.Shared/HTTP/DataLayer/Assets/IAssetDataLayer.cs +++ b/JMayer.Example.WebAssemblyBlazor.Shared/HTTP/DataLayer/Assets/IAssetDataLayer.cs @@ -6,7 +6,7 @@ namespace JMayer.Example.WebAssemblyBlazor.Shared.HTTP.DataLayer.Assets; /// /// The interface for interacting with a remote server using CRUD operations specifically for assets. /// -public interface IAssetDataLayer : IUserEditableDataLayer +public interface IAssetDataLayer : IStandardCRUDDataLayer { /// /// The method returns all the defined categories for the assets. diff --git a/JMayer.Example.WebAssemblyBlazor.Shared/HTTP/DataLayer/Assets/IStorageLocationDataLayer.cs b/JMayer.Example.WebAssemblyBlazor.Shared/HTTP/DataLayer/Assets/IStorageLocationDataLayer.cs index a55f232..44c2da0 100644 --- a/JMayer.Example.WebAssemblyBlazor.Shared/HTTP/DataLayer/Assets/IStorageLocationDataLayer.cs +++ b/JMayer.Example.WebAssemblyBlazor.Shared/HTTP/DataLayer/Assets/IStorageLocationDataLayer.cs @@ -6,6 +6,6 @@ namespace JMayer.Example.WebAssemblyBlazor.Shared.HTTP.DataLayer.Assets; /// /// The interface for interacting with a remote server using CRUD operations specifically for storage locations. /// -public interface IStorageLocationDataLayer : ISubUserEditableDataLayer +public interface IStorageLocationDataLayer : IStandardSubCRUDDataLayer { } diff --git a/JMayer.Example.WebAssemblyBlazor.Shared/HTTP/DataLayer/Assets/StorageLocationDataLayer.cs b/JMayer.Example.WebAssemblyBlazor.Shared/HTTP/DataLayer/Assets/StorageLocationDataLayer.cs index f3c2071..1f4b62f 100644 --- a/JMayer.Example.WebAssemblyBlazor.Shared/HTTP/DataLayer/Assets/StorageLocationDataLayer.cs +++ b/JMayer.Example.WebAssemblyBlazor.Shared/HTTP/DataLayer/Assets/StorageLocationDataLayer.cs @@ -6,7 +6,7 @@ namespace JMayer.Example.WebAssemblyBlazor.Shared.HTTP.DataLayer.Assets; /// /// The class manages CRUD interactions with a remote server for a storage location. /// -public class StorageLocationDataLayer : SubUserEditableDataLayer, IStorageLocationDataLayer +public class StorageLocationDataLayer : StandardSubCRUDDataLayer, IStorageLocationDataLayer { /// public StorageLocationDataLayer(HttpClient httpClient) : base(httpClient) { } diff --git a/JMayer.Example.WebAssemblyBlazor.Shared/HTTP/DataLayer/Parts/IPartDataLayer.cs b/JMayer.Example.WebAssemblyBlazor.Shared/HTTP/DataLayer/Parts/IPartDataLayer.cs index aa77405..517dc62 100644 --- a/JMayer.Example.WebAssemblyBlazor.Shared/HTTP/DataLayer/Parts/IPartDataLayer.cs +++ b/JMayer.Example.WebAssemblyBlazor.Shared/HTTP/DataLayer/Parts/IPartDataLayer.cs @@ -6,7 +6,7 @@ namespace JMayer.Example.WebAssemblyBlazor.Shared.HTTP.DataLayer.Parts; /// /// The interface for interacting with a remote server using CRUD operations specifically for parts. /// -public interface IPartDataLayer : IUserEditableDataLayer +public interface IPartDataLayer : IStandardCRUDDataLayer { /// /// The method returns all the defined categories for the parts. diff --git a/JMayer.Example.WebAssemblyBlazor.Shared/HTTP/DataLayer/Parts/IStockDataLayer.cs b/JMayer.Example.WebAssemblyBlazor.Shared/HTTP/DataLayer/Parts/IStockDataLayer.cs index d37804d..ed890a1 100644 --- a/JMayer.Example.WebAssemblyBlazor.Shared/HTTP/DataLayer/Parts/IStockDataLayer.cs +++ b/JMayer.Example.WebAssemblyBlazor.Shared/HTTP/DataLayer/Parts/IStockDataLayer.cs @@ -6,6 +6,6 @@ namespace JMayer.Example.WebAssemblyBlazor.Shared.HTTP.DataLayer.Parts; /// /// The interface for interacting with a remote server using CRUD operations specifically for part stock. /// -public interface IStockDataLayer : ISubUserEditableDataLayer +public interface IStockDataLayer : IStandardSubCRUDDataLayer { } diff --git a/JMayer.Example.WebAssemblyBlazor.Shared/HTTP/DataLayer/Parts/PartDataLayer.cs b/JMayer.Example.WebAssemblyBlazor.Shared/HTTP/DataLayer/Parts/PartDataLayer.cs index 91e1641..c6cdb15 100644 --- a/JMayer.Example.WebAssemblyBlazor.Shared/HTTP/DataLayer/Parts/PartDataLayer.cs +++ b/JMayer.Example.WebAssemblyBlazor.Shared/HTTP/DataLayer/Parts/PartDataLayer.cs @@ -8,7 +8,7 @@ namespace JMayer.Example.WebAssemblyBlazor.Shared.HTTP.DataLayer.Parts; /// /// The class manages CRUD interactions with a remote server for a part. /// -public class PartDataLayer : UserEditableDataLayer, IPartDataLayer +public class PartDataLayer : StandardCRUDDataLayer, IPartDataLayer { /// public PartDataLayer(HttpClient httpClient) : base(httpClient) { } @@ -19,7 +19,7 @@ public PartDataLayer(HttpClient httpClient) : base(httpClient) { } List? categories = null; HttpResponseMessage httpResponseMessage = await HttpClient.GetAsync($"api/{TypeName}/Category/All", cancellationToken); - if (httpResponseMessage.IsSuccessStatusCode && httpResponseMessage.StatusCode != HttpStatusCode.NoContent) + if (httpResponseMessage.IsSuccessStatusCode && httpResponseMessage.StatusCode is not HttpStatusCode.NoContent) { categories = await httpResponseMessage.Content.ReadFromJsonAsync>(cancellationToken); } diff --git a/JMayer.Example.WebAssemblyBlazor.Shared/HTTP/DataLayer/Parts/StockDataLayer.cs b/JMayer.Example.WebAssemblyBlazor.Shared/HTTP/DataLayer/Parts/StockDataLayer.cs index c5271e4..4c95562 100644 --- a/JMayer.Example.WebAssemblyBlazor.Shared/HTTP/DataLayer/Parts/StockDataLayer.cs +++ b/JMayer.Example.WebAssemblyBlazor.Shared/HTTP/DataLayer/Parts/StockDataLayer.cs @@ -6,7 +6,7 @@ namespace JMayer.Example.WebAssemblyBlazor.Shared.HTTP.DataLayer.Parts; /// /// The class manages CRUD interactions with a remote server for a part stock. /// -public class StockDataLayer : SubUserEditableDataLayer, IStockDataLayer +public class StockDataLayer : StandardSubCRUDDataLayer, IStockDataLayer { /// public StockDataLayer(HttpClient httpClient) : base(httpClient) { } diff --git a/JMayer.Example.WebAssemblyBlazor.Shared/JMayer.Example.WebAssemblyBlazor.Shared.csproj b/JMayer.Example.WebAssemblyBlazor.Shared/JMayer.Example.WebAssemblyBlazor.Shared.csproj index 8192819..4533e2c 100644 --- a/JMayer.Example.WebAssemblyBlazor.Shared/JMayer.Example.WebAssemblyBlazor.Shared.csproj +++ b/JMayer.Example.WebAssemblyBlazor.Shared/JMayer.Example.WebAssemblyBlazor.Shared.csproj @@ -1,14 +1,17 @@  - net8.0 + net9.0 enable enable - 8.0.0 + 9.0.0 + jmayer913 + jmayer913 + https://github.com/jmayer913/JMayer-Example-WebAssemblyBlazor - + diff --git a/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Components/Base/CardDialogBase.cs b/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Components/Base/CardDialogBase.cs index ac237c2..cceaa38 100644 --- a/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Components/Base/CardDialogBase.cs +++ b/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Components/Base/CardDialogBase.cs @@ -4,7 +4,6 @@ using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Forms; using MudBlazor; -using System.Net; namespace JMayer.Example.WebAssemblyBlazor.Client.Components.Base; @@ -14,8 +13,8 @@ namespace JMayer.Example.WebAssemblyBlazor.Client.Components.Base; /// Must be a UserEditableDataObject. /// Must be a IUserEditableDataLayer. public class CardDialogBase : ComponentBase - where T : SubUserEditableDataObject, new() - where U : ISubUserEditableDataLayer + where T : SubDataObject, new() + where U : IStandardSubCRUDDataLayer { /// /// The property gets/sets the data layer to be used by the dialog. @@ -72,7 +71,7 @@ public class CardDialogBase : ComponentBase protected override void OnParametersSet() { //For new records, set the owner to the value set when the dialog is opened. - if (DataObject.OwnerInteger64ID == 0) + if (DataObject.OwnerInteger64ID is 0) { DataObject.OwnerInteger64ID = OwnerId; } @@ -109,20 +108,20 @@ protected virtual async Task OnSubmitEditFormAsync() { MudDialog.Close(); } - else if (operationResult.ServerSideValidationResult?.Errors.Count > 0) + else if (operationResult.ValidationErrors.Count > 0) { Dictionary> errors = []; - foreach (ServerSideValidationError error in operationResult.ServerSideValidationResult.Errors) + foreach (var errorKeyPairs in operationResult.ValidationErrors) { - errors.Add(error.PropertyName, [error.ErrorMessage]); + errors.Add(errorKeyPairs.Key, [.. errorKeyPairs.Value]); } ServerSideValidation.DisplayErrors(errors); } - else if (operationResult.StatusCode == HttpStatusCode.Conflict) + else if (operationResult.ProblemDetails is not null) { - await DialogService.ShowEditConflictMessageAsync(); + await DialogService.ShowErrorMessageAsync(operationResult.ProblemDetails); } else { diff --git a/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Components/Base/EditableCardBase.cs b/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Components/Base/EditableCardBase.cs index 3514e2c..dd3f6de 100644 --- a/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Components/Base/EditableCardBase.cs +++ b/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Components/Base/EditableCardBase.cs @@ -13,8 +13,8 @@ namespace JMayer.Example.WebAssemblyBlazor.Client.Components.Base; /// Must be a ISubUserEditableDataLayer. /// Must be a component that's also a dialog. public class EditableCardBase : ComponentBase - where T : SubUserEditableDataObject - where U : ISubUserEditableDataLayer + where T : SubDataObject + where U : IStandardSubCRUDDataLayer where V : ComponentBase { /// @@ -62,7 +62,7 @@ protected virtual async Task> OnDataGridStateChangedAsync(GridState< { PagedList? pagedDataObjects = await DataLayer.GetPageAsync(OwnerID, gridState.ToQueryDefinition()); - if (pagedDataObjects != null) + if (pagedDataObjects is not null) { return new GridData() { @@ -88,26 +88,32 @@ protected virtual async Task OnDeleteButtonClickAsync(T dataObject) { bool? result = await DialogService.ShowConfirmActionMessageAsync(); - if (result == true) + if (result is not true) { - try - { - OperationResult operationResult = await DataLayer.DeleteAsync(dataObject); + return; + } - if (operationResult.IsSuccessStatusCode) - { - await MudDataGrid.ReloadServerData(); - } - else - { - await DialogService.ShowErrorMessageAsync("Failed to delete the object because of an error on the server."); - } + try + { + OperationResult operationResult = await DataLayer.DeleteAsync(dataObject); + + if (operationResult.IsSuccessStatusCode) + { + await MudDataGrid.ReloadServerData(); + } + else if (operationResult.ProblemDetails is not null) + { + await DialogService.ShowErrorMessageAsync(operationResult.ProblemDetails); } - catch + else { - await DialogService.ShowErrorMessageAsync("Failed to communicate with the server."); + await DialogService.ShowErrorMessageAsync("Failed to delete the object because of an error on the server."); } } + catch + { + await DialogService.ShowErrorMessageAsync("Failed to communicate with the server."); + } } /// @@ -124,7 +130,7 @@ protected virtual async Task OnEditButtonClickAsync(T dataObject) IDialogReference dialogReference = await DialogService.ShowAsync($"Edit the {DataObjectTypeName.SpaceCapitalLetters()}", dialogParameters); DialogResult? dialogResult = await dialogReference.Result; - if (dialogResult?.Canceled == false) + if (dialogResult?.Canceled is false) { await MudDataGrid.ReloadServerData(); } @@ -144,7 +150,7 @@ protected virtual async Task OnNewButtonClickAsync() IDialogReference dialogReference = await DialogService.ShowAsync($"Create a New {DataObjectTypeName.SpaceCapitalLetters()}", dialogParameters); DialogResult? dialogResult = await dialogReference.Result; - if (dialogResult?.Canceled == false) + if (dialogResult?.Canceled is false) { await MudDataGrid.ReloadServerData(); } diff --git a/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Components/Base/InspectorBase.cs b/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Components/Base/InspectorBase.cs index c1d58ed..2c4f7a4 100644 --- a/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Components/Base/InspectorBase.cs +++ b/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Components/Base/InspectorBase.cs @@ -1,6 +1,8 @@ using JMayer.Data.Data; using JMayer.Data.HTTP.DataLayer; +using JMayer.Example.WebAssemblyBlazor.Client.Extensions; using Microsoft.AspNetCore.Components; +using MudBlazor; namespace JMayer.Example.WebAssemblyBlazor.Client.Components.Base; @@ -10,8 +12,8 @@ namespace JMayer.Example.WebAssemblyBlazor.Client.Components.Base; /// Must be a UserEditableDataObject. /// Must be a IUserEditableDataLayer. public class InspectorBase : ComponentBase - where T : UserEditableDataObject - where U : IUserEditableDataLayer + where T : DataObject + where U : IStandardCRUDDataLayer { /// /// The property gets/sets the data layer to used by the page. @@ -24,6 +26,12 @@ public class InspectorBase : ComponentBase /// protected T? DataObject { get; set; } + /// + /// The property gets/sets the dialog service used for managing MudDialogs. + /// + [Inject] + protected IDialogService DialogService { get; set; } = default!; + /// /// The property gets/sets the index key for the data object. /// @@ -41,7 +49,15 @@ public class InspectorBase : ComponentBase /// A Task object for the async. protected override async Task OnParametersSetAsync() { - DataObject = await DataLayer.GetSingleAsync(IndexKey); + try + { + DataObject = await DataLayer.GetSingleAsync(IndexKey); + } + catch + { + await DialogService.ShowErrorMessageAsync("Failed to communicate with the server."); + } + await base.OnParametersSetAsync(); Initialized = true; } diff --git a/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Components/Base/NewDialogBase.cs b/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Components/Base/NewDialogBase.cs index 08972dd..963969e 100644 --- a/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Components/Base/NewDialogBase.cs +++ b/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Components/Base/NewDialogBase.cs @@ -13,8 +13,8 @@ namespace JMayer.Example.WebAssemblyBlazor.Client.Components.Base; /// Must be a UserEditableDataObject. /// Must be a IUserEditableDataLayer. public class NewDialogBase : ComponentBase - where T : UserEditableDataObject, new() - where U : IUserEditableDataLayer + where T : DataObject, new() + where U : IStandardCRUDDataLayer { /// /// The property gets/sets the data layer to be used by the dialog. @@ -77,17 +77,21 @@ protected virtual async Task OnSubmitEditFormAsync() { MudDialog.Close(); } - else if (operationResult.ServerSideValidationResult?.Errors.Count > 0) + else if (operationResult.ValidationErrors.Count > 0) { Dictionary> errors = []; - foreach (ServerSideValidationError error in operationResult.ServerSideValidationResult.Errors) + foreach (var errorKeyPair in operationResult.ValidationErrors) { - errors.Add(error.PropertyName, [error.ErrorMessage]); + errors.Add(errorKeyPair.Key, [.. errorKeyPair.Value]); } ServerSideValidation.DisplayErrors(errors); } + else if (operationResult.ProblemDetails is not null) + { + await DialogService.ShowErrorMessageAsync(operationResult.ProblemDetails); + } else { await DialogService.ShowErrorMessageAsync("Failed to create the object because of an error on the server."); diff --git a/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Components/Base/OverviewCardBase.cs b/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Components/Base/OverviewCardBase.cs index 741b1d1..b3cf707 100644 --- a/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Components/Base/OverviewCardBase.cs +++ b/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Components/Base/OverviewCardBase.cs @@ -4,7 +4,6 @@ using Microsoft.AspNetCore.Components; using Microsoft.AspNetCore.Components.Forms; using MudBlazor; -using System.Net; namespace JMayer.Example.WebAssemblyBlazor.Client.Components.Base; @@ -14,8 +13,8 @@ namespace JMayer.Example.WebAssemblyBlazor.Client.Components.Base; /// Must be a UserEditableDataObject. /// Must be a IUserEditableDataLayer. public class OverviewCardBase : ComponentBase - where T : UserEditableDataObject, new() - where U : IUserEditableDataLayer + where T : DataObject, new() + where U : IStandardCRUDDataLayer { /// /// The property gets/sets the data layer to used by the page. @@ -76,8 +75,9 @@ protected override void OnParametersSet() /// protected virtual void OnResetClick() { + //For some reason, EditContext.MarkAsUnmodified() does not work and I need to recreate the EditContext. DataObject.MapProperties(OriginalDataObject); - EditContext.MarkAsUnmodified(); + EditContext = new EditContext(DataObject); } /// @@ -90,27 +90,27 @@ protected virtual async Task OnSubmitFormAsync() { OperationResult operationResult = await DataLayer.UpdateAsync(DataObject); - if (operationResult.IsSuccessStatusCode && operationResult.DataObject != null) + if (operationResult.IsSuccessStatusCode && operationResult.DataObject is not null) { Updated = true; DataObject.MapProperties((T)operationResult.DataObject); OriginalDataObject.MapProperties(DataObject); EditContext.MarkAsUnmodified(); } - else if (operationResult.ServerSideValidationResult?.Errors.Count > 0) + else if (operationResult.ValidationErrors.Count > 0) { Dictionary> errors = []; - foreach (ServerSideValidationError error in operationResult.ServerSideValidationResult.Errors) + foreach (var errorKeyPair in operationResult.ValidationErrors) { - errors.Add(error.PropertyName, [error.ErrorMessage]); + errors.Add(errorKeyPair.Key, [.. errorKeyPair.Value]); } ServerSideValidation.DisplayErrors(errors); } - else if (operationResult.StatusCode == HttpStatusCode.Conflict) + else if (operationResult.ProblemDetails is not null) { - await DialogService.ShowEditConflictMessageAsync(); + await DialogService.ShowErrorMessageAsync(operationResult.ProblemDetails); } else { diff --git a/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Components/Base/SearchBase.cs b/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Components/Base/SearchBase.cs index 6cf36ed..27c2962 100644 --- a/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Components/Base/SearchBase.cs +++ b/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Components/Base/SearchBase.cs @@ -13,8 +13,8 @@ namespace JMayer.Example.WebAssemblyBlazor.Client.Components.Base; /// Must be a IUserEditableDataLayer. /// Must be a component that's also a dialog. public class SearchBase : ComponentBase - where T : UserEditableDataObject - where U : IUserEditableDataLayer + where T : DataObject + where U : IStandardCRUDDataLayer where V : ComponentBase { /// @@ -56,7 +56,7 @@ protected virtual async Task> OnDataGridStateChangedAsync(GridState< { PagedList? pagedDataObjects = await DataLayer.GetPageAsync(gridState.ToQueryDefinition()); - if (pagedDataObjects != null) + if (pagedDataObjects is not null) { return new GridData() { @@ -82,25 +82,31 @@ protected virtual async Task OnDeleteButtonClickAsync(T dataObject) { bool? result = await DialogService.ShowConfirmActionMessageAsync(); - if (result == true) + if (result is not true) { - try - { - OperationResult operationResult = await DataLayer.DeleteAsync(dataObject); + return; + } - if (operationResult.IsSuccessStatusCode) - { - await MudDataGrid.ReloadServerData(); - } - else - { - await DialogService.ShowErrorMessageAsync("Failed to delete the object because of an error on the server."); - } + try + { + OperationResult operationResult = await DataLayer.DeleteAsync(dataObject); + + if (operationResult.IsSuccessStatusCode) + { + await MudDataGrid.ReloadServerData(); } - catch + else if (operationResult.ProblemDetails is not null) { - await DialogService.ShowErrorMessageAsync("Failed to communicate with the server."); + await DialogService.ShowErrorMessageAsync(operationResult.ProblemDetails); } + else + { + await DialogService.ShowErrorMessageAsync("Failed to delete the object because of an error on the server."); + } + } + catch + { + await DialogService.ShowErrorMessageAsync("Failed to communicate with the server."); } } @@ -109,9 +115,7 @@ protected virtual async Task OnDeleteButtonClickAsync(T dataObject) /// /// The data object to inspect. protected virtual void OnEditButtonClick(T dataObject) - { - NavigationManager.NavigateTo($"/{DataObjectTypeName}/{dataObject.Integer64ID}"); - } + => NavigationManager.NavigateTo($"/{DataObjectTypeName}/{dataObject.Integer64ID}"); /// /// The method opens a dialog for creating a new data object and if not canceled, refreshes the data grid. @@ -122,7 +126,7 @@ protected virtual async Task OnNewButtonClickAsync() IDialogReference dialogReference = await DialogService.ShowAsync($"Create a New {DataObjectTypeName.SpaceCapitalLetters()}"); DialogResult? dialogResult = await dialogReference.Result; - if (dialogResult?.Canceled == false) + if (dialogResult?.Canceled is false) { await MudDataGrid.ReloadServerData(); } diff --git a/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Components/DataObjectTimestamp.razor b/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Components/DataObjectTimestamp.razor index 591c598..6c3c908 100644 --- a/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Components/DataObjectTimestamp.razor +++ b/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Components/DataObjectTimestamp.razor @@ -1,4 +1,4 @@ -@typeparam T where T : UserEditableDataObject +@typeparam T where T : DataObject @implements IDisposable @if (Updated) diff --git a/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Components/ServerSideValidation.cs b/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Components/ServerSideValidation.cs index 6701c8a..fe62767 100644 --- a/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Components/ServerSideValidation.cs +++ b/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Components/ServerSideValidation.cs @@ -25,15 +25,17 @@ public class ServerSideValidation : ComponentBase /// The errors to display. public void DisplayErrors(Dictionary> errors) { - if (_store != null) + if (_store is null) { - foreach (var error in errors) - { - _store.Add(EditContext.Field(error.Key), error.Value); - } + return; + } - EditContext.NotifyValidationStateChanged(); + foreach (var error in errors) + { + _store.Add(EditContext.Field(error.Key), error.Value); } + + EditContext.NotifyValidationStateChanged(); } /// diff --git a/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Extensions/GridStateExtension.cs b/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Extensions/GridStateExtension.cs index 03eb918..945ba62 100644 --- a/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Extensions/GridStateExtension.cs +++ b/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Extensions/GridStateExtension.cs @@ -65,7 +65,7 @@ public static QueryDefinition ToQueryDefinition(this GridState gridState) { string value = string.Empty; - if (filterDefinition.Value != null && !string.IsNullOrWhiteSpace(filterDefinition.Value.ToString())) + if (filterDefinition.Value is not null && string.IsNullOrWhiteSpace(filterDefinition.Value.ToString()) is false) { if (filterDefinition.Value is DateTime dateTime) { diff --git a/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Extensions/IDialogServiceExtension.cs b/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Extensions/IDialogServiceExtension.cs index 180400e..8e8a9fb 100644 --- a/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Extensions/IDialogServiceExtension.cs +++ b/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Extensions/IDialogServiceExtension.cs @@ -14,20 +14,7 @@ public static class IDialogServiceExtension /// The message to display to the user. /// True means the user confirm the action; false or null means the user didn't. public static async Task ShowConfirmActionMessageAsync(this IDialogService dialogService, string message = "Do you want to confirm this action?") - { - return await dialogService.ShowMessageBox("Warning", message, cancelText: "Cancel"); - } - - /// - /// The method displays an edit conflict message to the user. - /// - /// The dialog service used to display the error message. - /// The message to display to the user. - /// A Task object for the async. - public static async Task ShowEditConflictMessageAsync(this IDialogService dialogService, string message = "The submitted data was detected to be out of date; please refresh and try again.") - { - _ = await dialogService.ShowMessageBox("Warning", message, options: new DialogOptions() { CloseButton = false }); - } + => _ = await dialogService.ShowMessageBox("Warning", message, cancelText: "Cancel"); /// /// The method displays an error message to the user. @@ -36,7 +23,5 @@ public static class IDialogServiceExtension /// The message to display to the user. /// A Task object for the async. public static async Task ShowErrorMessageAsync(this IDialogService dialogService, string message) - { - _ = await dialogService.ShowMessageBox("Error", message, options: new DialogOptions() { CloseButton = false }); - } + => _ = await dialogService.ShowMessageBox("Error", message, options: new DialogOptions() { CloseButton = false }); } diff --git a/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Extensions/StringExtension.cs b/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Extensions/StringExtension.cs index 8efe686..89e1521 100644 --- a/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Extensions/StringExtension.cs +++ b/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Extensions/StringExtension.cs @@ -1,4 +1,6 @@ -namespace JMayer.Example.WebAssemblyBlazor.Client.Extensions; +using System.Text.RegularExpressions; + +namespace JMayer.Example.WebAssemblyBlazor.Client.Extensions; /// /// The static class contains extension methods for the string class. @@ -10,8 +12,5 @@ public static class StringExtension /// /// The string to space. /// A string. - public static string SpaceCapitalLetters(this string value) - { - return string.Concat(value.Select(c => char.IsUpper(c) ? $" {c}" : c.ToString())).TrimStart(); - } + public static string SpaceCapitalLetters(this string value) => Regex.Replace(value, "([A-Z])", " $1").Trim(); } diff --git a/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/JMayer.Example.WebAssemblyBlazor.Client.csproj b/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/JMayer.Example.WebAssemblyBlazor.Client.csproj index fbbe5fa..db25016 100644 --- a/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/JMayer.Example.WebAssemblyBlazor.Client.csproj +++ b/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/JMayer.Example.WebAssemblyBlazor.Client.csproj @@ -1,17 +1,20 @@  - net8.0 + net9.0 enable enable true Default - 8.0.0 + 9.0.0 + jmayer913 + jmayer913 + https://github.com/jmayer913/JMayer-Example-WebAssemblyBlazor - - + + diff --git a/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Pages/Assets/Cards/OverviewCard.razor b/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Pages/Assets/Cards/OverviewCard.razor index b94e5b0..ba68ac9 100644 --- a/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Pages/Assets/Cards/OverviewCard.razor +++ b/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Pages/Assets/Cards/OverviewCard.razor @@ -23,7 +23,7 @@ - + diff --git a/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Pages/Assets/Cards/OverviewCardBase.cs b/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Pages/Assets/Cards/OverviewCardBase.cs index 69d8283..ab05fb7 100644 --- a/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Pages/Assets/Cards/OverviewCardBase.cs +++ b/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Pages/Assets/Cards/OverviewCardBase.cs @@ -1,4 +1,5 @@ using JMayer.Data.Data; +using JMayer.Example.WebAssemblyBlazor.Client.Extensions; using JMayer.Example.WebAssemblyBlazor.Shared.Data.Assets; using JMayer.Example.WebAssemblyBlazor.Shared.HTTP.DataLayer.Assets; @@ -9,34 +10,15 @@ namespace JMayer.Example.WebAssemblyBlazor.Client.Pages.Assets.Cards; /// public class OverviewCardBase : Components.Base.OverviewCardBase { - /// - /// The selected asset parent. - /// - private ListView? _selectedAssetParent; - - /// - /// The property gets/sets the assets to choose for a parent. - /// - protected List Assets { get; set; } = []; - /// /// The property gets/sets the categories for the parts. /// protected List Categories { get; set; } = []; /// - /// The property gets/sets the parent asset which the user selected. + /// The property gets/sets the assets to choose for a parent. /// - protected ListView? SelectedAssetParent - { - get => _selectedAssetParent; - set - { - _selectedAssetParent = value; - //Map any selection changes to the ParentID property. - DataObject.ParentID = value?.Integer64ID ?? 0; - } - } + protected List ParentAssets { get; set; } = []; /// /// The method sets up the component after the parameters are set. @@ -44,22 +26,20 @@ protected ListView? SelectedAssetParent /// protected override async Task OnParametersSetAsync() { - Assets = await DataLayer.GetAllListViewAsync() ?? []; - Categories = await DataLayer.GetCategoriesAsync() ?? []; + Task?> categoryTask = DataLayer.GetCategoriesAsync(); + Task?> parentAssetTask = DataLayer.GetAllListViewAsync(); - if (DataObject.ParentID != null) + try { - Asset? parent = await DataLayer.GetSingleAsync(DataObject.ParentID ?? 0); - - if (parent != null) - { - SelectedAssetParent = new ListView() - { - Integer64ID = parent.Integer64ID, - Name = parent.Name, - }; - } + await Task.WhenAll(categoryTask, parentAssetTask); + } + catch + { + await DialogService.ShowErrorMessageAsync("Failed to communicate with the server."); } + + Categories = categoryTask.Result ?? []; + ParentAssets = parentAssetTask.Result ?? []; await base.OnParametersSetAsync(); } @@ -70,15 +50,15 @@ protected override async Task OnParametersSetAsync() /// The value to search for. /// Used to cancel the task. /// A list of acceptable categories. - protected async Task> OnAssetParentAutoCompleteSearchAsync(string value, CancellationToken cancellationToken) + protected async Task> OnAssetParentAutoCompleteSearchAsync(string value, CancellationToken cancellationToken) { if (string.IsNullOrWhiteSpace(value)) { - return await Task.FromResult(Assets); + return await Task.FromResult(ParentAssets.Select(obj => (long?)obj.Integer64ID)); } else { - return await Task.FromResult(Assets.Where(obj => obj.Name.Contains(value))); + return await Task.FromResult(ParentAssets.Where(obj => obj.Name.Contains(value)).Select(obj => (long?)obj.Integer64ID)); } } diff --git a/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Pages/Assets/Dialogs/NewAssetDialog.razor b/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Pages/Assets/Dialogs/NewAssetDialog.razor index b84e4f4..79da723 100644 --- a/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Pages/Assets/Dialogs/NewAssetDialog.razor +++ b/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Pages/Assets/Dialogs/NewAssetDialog.razor @@ -16,7 +16,7 @@ Group Equipment - + diff --git a/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Pages/Assets/Dialogs/NewAssetDialogBase.cs b/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Pages/Assets/Dialogs/NewAssetDialogBase.cs index b51d647..3d758f4 100644 --- a/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Pages/Assets/Dialogs/NewAssetDialogBase.cs +++ b/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Pages/Assets/Dialogs/NewAssetDialogBase.cs @@ -1,5 +1,6 @@ using JMayer.Data.Data; using JMayer.Example.WebAssemblyBlazor.Client.Components.Base; +using JMayer.Example.WebAssemblyBlazor.Client.Extensions; using JMayer.Example.WebAssemblyBlazor.Shared.Data.Assets; using JMayer.Example.WebAssemblyBlazor.Shared.HTTP.DataLayer.Assets; @@ -11,33 +12,14 @@ namespace JMayer.Example.WebAssemblyBlazor.Client.Pages.Assets.Dialogs; public class NewAssetDialogBase : NewDialogBase { /// - /// The selected asset parent. + /// The property gets/sets the categories for the parts. /// - private ListView? _selectedAssetParent; + protected List Categories { get; set; } = []; /// /// The property gets/sets the assets to choose for a parent. /// - protected List Assets { get; set; } = []; - - /// - /// The property gets/sets the categories for the parts. - /// - protected List Categories { get; set; } = []; - - /// - /// The property gets/sets the parent asset which the user selected. - /// - protected ListView? SelectedAssetParent - { - get => _selectedAssetParent; - set - { - _selectedAssetParent = value; - //Map any selection changes to the ParentID property. - DataObject.ParentID = value?.Integer64ID ?? 0; - } - } + protected List ParentAssets { get; set; } = []; /// /// The method sets up the component after the parameters are set. @@ -45,8 +27,21 @@ protected ListView? SelectedAssetParent /// A Task object for the async. protected override async Task OnParametersSetAsync() { - Assets = await DataLayer.GetAllListViewAsync() ?? []; - Categories = await DataLayer.GetCategoriesAsync() ?? []; + Task?> categoryTask = DataLayer.GetCategoriesAsync(); + Task?> parentAssetTask = DataLayer.GetAllListViewAsync(); + + try + { + await Task.WhenAll(categoryTask, parentAssetTask); + } + catch + { + await DialogService.ShowErrorMessageAsync("Failed to communicate with the server."); + } + + Categories = categoryTask.Result ?? []; + ParentAssets = parentAssetTask.Result ?? []; + await base.OnParametersSetAsync(); } @@ -55,16 +50,16 @@ protected override async Task OnParametersSetAsync() /// /// The value to search for. /// Used to cancel the task. - /// A list of acceptable categories. - protected async Task> OnAssetParentAutoCompleteSearchAsync(string value, CancellationToken cancellationToken) + /// A list of acceptable parent assets. + protected async Task> OnAssetParentAutoCompleteSearchAsync(string value, CancellationToken cancellationToken) { if (string.IsNullOrWhiteSpace(value)) { - return await Task.FromResult(Assets); + return await Task.FromResult(ParentAssets.Select(obj => (long?)obj.Integer64ID)); } else { - return await Task.FromResult(Assets.Where(obj => obj.Name.Contains(value))); + return await Task.FromResult(ParentAssets.Where(obj => obj.Name.Contains(value)).Select(obj => (long?)obj.Integer64ID)); } } diff --git a/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Pages/Assets/Dialogs/StorageLocationDialogBase.cs b/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Pages/Assets/Dialogs/StorageLocationDialogBase.cs index b9e1898..928e6ef 100644 --- a/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Pages/Assets/Dialogs/StorageLocationDialogBase.cs +++ b/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Pages/Assets/Dialogs/StorageLocationDialogBase.cs @@ -1,7 +1,6 @@ using JMayer.Example.WebAssemblyBlazor.Client.Components.Base; using JMayer.Example.WebAssemblyBlazor.Shared.Data.Assets; using JMayer.Example.WebAssemblyBlazor.Shared.HTTP.DataLayer.Assets; -using Microsoft.AspNetCore.Components; namespace JMayer.Example.WebAssemblyBlazor.Client.Pages.Assets.Dialogs; @@ -11,21 +10,9 @@ namespace JMayer.Example.WebAssemblyBlazor.Client.Pages.Assets.Dialogs; public class StorageLocationDialogBase : CardDialogBase { /// - public override Task SetParametersAsync(ParameterView parameters) - { - //The Name property is a required field but the - //StorageLocation data object doesn't use it so - //it needs to be set to pass validation. - DataObject.Name = "A Name"; - return base.SetParametersAsync(parameters); - } - - /// + /// Overridden because the Name property is used for the ListView and we need to make sure the Name property is set to the FriendlyName. protected override async Task OnSubmitEditFormAsync() { - //The Name property is used for the ListView and - //this ensures the ListView displays the friendly - //name. DataObject.Name = DataObject.FriendlyName; await base.OnSubmitEditFormAsync(); } diff --git a/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Pages/NotFound.razor b/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Pages/NotFound.razor index 06a5868..faade9d 100644 --- a/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Pages/NotFound.razor +++ b/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Pages/NotFound.razor @@ -1,7 +1,3 @@ @page "/NotFound" Sorry, the page was not found. - -@code { - -} diff --git a/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Pages/Parts/Cards/OverviewCardBase.cs b/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Pages/Parts/Cards/OverviewCardBase.cs index fab8782..7b05177 100644 --- a/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Pages/Parts/Cards/OverviewCardBase.cs +++ b/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Pages/Parts/Cards/OverviewCardBase.cs @@ -1,4 +1,5 @@ -using JMayer.Example.WebAssemblyBlazor.Shared.Data.Parts; +using JMayer.Example.WebAssemblyBlazor.Client.Extensions; +using JMayer.Example.WebAssemblyBlazor.Shared.Data.Parts; using JMayer.Example.WebAssemblyBlazor.Shared.HTTP.DataLayer.Parts; namespace JMayer.Example.WebAssemblyBlazor.Client.Pages.Parts.Cards; @@ -19,7 +20,15 @@ public class OverviewCardBase : Components.Base.OverviewCardBase protected override async Task OnParametersSetAsync() { - Categories = await DataLayer.GetCategoriesAsync() ?? []; + try + { + Categories = await DataLayer.GetCategoriesAsync() ?? []; + } + catch + { + await DialogService.ShowErrorMessageAsync("Failed to communicate with the server."); + } + await base.OnParametersSetAsync(); } diff --git a/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Pages/Parts/Dialogs/NewPartDialogBase.cs b/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Pages/Parts/Dialogs/NewPartDialogBase.cs index 50c4a96..f9946f4 100644 --- a/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Pages/Parts/Dialogs/NewPartDialogBase.cs +++ b/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Pages/Parts/Dialogs/NewPartDialogBase.cs @@ -1,4 +1,5 @@ using JMayer.Example.WebAssemblyBlazor.Client.Components.Base; +using JMayer.Example.WebAssemblyBlazor.Client.Extensions; using JMayer.Example.WebAssemblyBlazor.Shared.Data.Parts; using JMayer.Example.WebAssemblyBlazor.Shared.HTTP.DataLayer.Parts; @@ -20,7 +21,15 @@ public class NewPartDialogBase : NewDialogBase /// protected override async Task OnParametersSetAsync() { - Categories = await DataLayer.GetCategoriesAsync() ?? []; + try + { + Categories = await DataLayer.GetCategoriesAsync() ?? []; + } + catch + { + await DialogService.ShowErrorMessageAsync("Failed to communicate with the server."); + } + await base.OnParametersSetAsync(); } diff --git a/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Pages/Parts/Dialogs/StockDialog.razor b/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Pages/Parts/Dialogs/StockDialog.razor index 40df8cc..852004d 100644 --- a/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Pages/Parts/Dialogs/StockDialog.razor +++ b/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Pages/Parts/Dialogs/StockDialog.razor @@ -11,11 +11,11 @@ @if (IsNewRecord) { - + } - else if (SelectedStorageLocation != null) + else { - + } diff --git a/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Pages/Parts/Dialogs/StockDialogBase.cs b/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Pages/Parts/Dialogs/StockDialogBase.cs index 6a8b885..0ee118b 100644 --- a/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Pages/Parts/Dialogs/StockDialogBase.cs +++ b/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.Client/Pages/Parts/Dialogs/StockDialogBase.cs @@ -1,6 +1,6 @@ using JMayer.Data.Data; using JMayer.Example.WebAssemblyBlazor.Client.Components.Base; -using JMayer.Example.WebAssemblyBlazor.Shared.Data.Assets; +using JMayer.Example.WebAssemblyBlazor.Client.Extensions; using JMayer.Example.WebAssemblyBlazor.Shared.Data.Parts; using JMayer.Example.WebAssemblyBlazor.Shared.HTTP.DataLayer.Assets; using JMayer.Example.WebAssemblyBlazor.Shared.HTTP.DataLayer.Parts; @@ -13,11 +13,6 @@ namespace JMayer.Example.WebAssemblyBlazor.Client.Pages.Parts.Dialogs; /// public class StockDialogBase : CardDialogBase { - /// - /// The selected storage location. - /// - private ListView? _selectedStorageLocation; - /// /// The property gets/sets the data layer needed to access storage locations. /// @@ -29,54 +24,19 @@ public class StockDialogBase : CardDialogBase /// protected List StorageLocations { get; set; } = []; - /// - /// The property gets/sets the storage location which the user selected. - /// - protected ListView? SelectedStorageLocation - { - get => _selectedStorageLocation; - set - { - _selectedStorageLocation = value; - //Map any selection changes to the StorageLocationId and StorageLocationName property. - DataObject.StorageLocationID = value?.Integer64ID ?? 0; - DataObject.StorageLocationName = value?.Name ?? string.Empty; - } - } - - /// - /// The method sets up the component after the parameters are set. - /// - /// A Task object for the async. - protected override void OnParametersSet() - { - //The Name property is a required field but the - //Stock data object doesn't use it so - //it needs to be set to pass validation. - DataObject.Name = "A Name"; - base.OnParametersSet(); - } - /// /// The method sets up the component after the parameters are set. /// /// A Task object for the async. protected override async Task OnParametersSetAsync() { - StorageLocations = await StorageLocationDataLayer.GetAllListViewAsync() ?? []; - - if (!IsNewRecord) + try { - StorageLocation? storageLocation = await StorageLocationDataLayer.GetSingleAsync(DataObject.StorageLocationID); - - if (storageLocation != null) - { - SelectedStorageLocation = new ListView() - { - Integer64ID = storageLocation.Integer64ID, - Name = storageLocation.Name, - }; - } + StorageLocations = await StorageLocationDataLayer.GetAllListViewAsync() ?? []; + } + catch + { + await DialogService.ShowErrorMessageAsync("Failed to communicate with the server."); } await base.OnParametersSetAsync(); @@ -87,16 +47,25 @@ protected override async Task OnParametersSetAsync() /// /// The value to search for. /// Used to cancel the task. - /// A list of acceptable categories. - protected async Task> OnStorageLocationAutoCompleteSearchAsync(string value, CancellationToken cancellationToken) + /// A list of acceptable storage locations. + protected async Task> OnStorageLocationAutoCompleteSearchAsync(string value, CancellationToken cancellationToken) { if (string.IsNullOrWhiteSpace(value)) { - return await Task.FromResult(StorageLocations); + return await Task.FromResult(StorageLocations.Select(obj => obj.Integer64ID)); } else { - return await Task.FromResult(StorageLocations.Where(obj => obj.Name.Contains(value))); + return await Task.FromResult(StorageLocations.Where(obj => obj.Name.Contains(value)).Select(obj => obj.Integer64ID)); } } + + /// + /// Overridden so the stock's StorageLocationName is updated before it's submitted to the server. + protected override async Task OnSubmitEditFormAsync() + { + ListView? selected = StorageLocations.FirstOrDefault(obj => obj.Integer64ID == DataObject.StorageLocationID); + DataObject.StorageLocationName = selected?.Name ?? string.Empty; + await base.OnSubmitEditFormAsync(); + } } diff --git a/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor/Controllers/Assets/AssetController.cs b/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor/Controllers/Assets/AssetController.cs index f96fbbe..b31eabe 100644 --- a/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor/Controllers/Assets/AssetController.cs +++ b/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor/Controllers/Assets/AssetController.cs @@ -1,6 +1,7 @@ using JMayer.Example.WebAssemblyBlazor.Shared.Data.Assets; using JMayer.Example.WebAssemblyBlazor.Shared.Database.DataLayer.Assets; -using JMayer.Web.Mvc.Controller; +using JMayer.Web.Mvc.Controller.Api; +using JMayer.Web.Mvc.Extension; using Microsoft.AspNetCore.Mvc; namespace JMayer.Example.WebAssemblyBlazor.Controllers.Assets; @@ -10,7 +11,7 @@ namespace JMayer.Example.WebAssemblyBlazor.Controllers.Assets; /// [Route("api/[controller]")] [ApiController] -public class AssetController : UserEditableController +public class AssetController : StandardCRUDController { /// public AssetController(IAssetDataLayer dataLayer, ILogger logger) : base(dataLayer, logger) { } @@ -30,8 +31,8 @@ public async Task GetCategoriesAsync() } catch (Exception ex) { - Logger.LogError(ex, "Failed to return the asset categories."); - return Problem(); + Logger.LogError(ex, "Failed to return the {Type} categories.", DataObjectTypeName); + return Problem(title: $"{DataObjectTypeName} Get Categories Error", detail: $"Failed to return the {DataObjectTypeName.SpaceCapitalLetters()} categories records because of an error on the server."); } } } diff --git a/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor/Controllers/Assets/StorageLocationController.cs b/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor/Controllers/Assets/StorageLocationController.cs index 6b93076..b55008e 100644 --- a/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor/Controllers/Assets/StorageLocationController.cs +++ b/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor/Controllers/Assets/StorageLocationController.cs @@ -1,6 +1,6 @@ using JMayer.Example.WebAssemblyBlazor.Shared.Data.Assets; using JMayer.Example.WebAssemblyBlazor.Shared.Database.DataLayer.Assets; -using JMayer.Web.Mvc.Controller; +using JMayer.Web.Mvc.Controller.Api; using Microsoft.AspNetCore.Mvc; namespace JMayer.Example.WebAssemblyBlazor.Controllers.Assets; @@ -10,7 +10,7 @@ namespace JMayer.Example.WebAssemblyBlazor.Controllers.Assets; /// [Route("api/[controller]")] [ApiController] -public class StorageLocationController : SubUserEditableController +public class StorageLocationController : StandardSubCRUDController { /// public StorageLocationController(IStorageLocationDataLayer dataLayer, ILogger logger) : base(dataLayer, logger) { } diff --git a/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor/Controllers/Parts/PartController.cs b/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor/Controllers/Parts/PartController.cs index b722ab9..e1efa09 100644 --- a/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor/Controllers/Parts/PartController.cs +++ b/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor/Controllers/Parts/PartController.cs @@ -1,6 +1,7 @@ using JMayer.Example.WebAssemblyBlazor.Shared.Data.Parts; using JMayer.Example.WebAssemblyBlazor.Shared.Database.DataLayer.Parts; -using JMayer.Web.Mvc.Controller; +using JMayer.Web.Mvc.Controller.Api; +using JMayer.Web.Mvc.Extension; using Microsoft.AspNetCore.Mvc; namespace JMayer.Example.WebAssemblyBlazor.Controllers.Parts; @@ -10,7 +11,7 @@ namespace JMayer.Example.WebAssemblyBlazor.Controllers.Parts; /// [Route("api/[controller]")] [ApiController] -public class PartController : UserEditableController +public class PartController : StandardCRUDController { /// public PartController(IPartDataLayer dataLayer, ILogger logger) : base(dataLayer, logger) { } @@ -30,8 +31,8 @@ public async Task GetCategoriesAsync() } catch (Exception ex) { - Logger.LogError(ex, "Failed to return the part categories."); - return Problem(); + Logger.LogError(ex, "Failed to return the {Type} categories.", DataObjectTypeName); + return Problem(title: $"{DataObjectTypeName} Get Categories Error", detail: $"Failed to return the {DataObjectTypeName.SpaceCapitalLetters()} categories records because of an error on the server."); } } } diff --git a/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor/Controllers/Parts/StockController.cs b/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor/Controllers/Parts/StockController.cs index a0aefb6..f3246f5 100644 --- a/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor/Controllers/Parts/StockController.cs +++ b/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor/Controllers/Parts/StockController.cs @@ -1,6 +1,6 @@ using JMayer.Example.WebAssemblyBlazor.Shared.Data.Parts; using JMayer.Example.WebAssemblyBlazor.Shared.Database.DataLayer.Parts; -using JMayer.Web.Mvc.Controller; +using JMayer.Web.Mvc.Controller.Api; using Microsoft.AspNetCore.Mvc; namespace JMayer.Example.WebAssemblyBlazor.Controllers.Parts; @@ -10,7 +10,7 @@ namespace JMayer.Example.WebAssemblyBlazor.Controllers.Parts; /// [Route("api/[controller]")] [ApiController] -public class StockController : SubUserEditableController +public class StockController : StandardSubCRUDController { /// public StockController(IStockDataLayer dataLayer, ILogger logger) : base(dataLayer, logger) { } diff --git a/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.csproj b/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.csproj index 5c15fb7..a513fd0 100644 --- a/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.csproj +++ b/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor/JMayer.Example.WebAssemblyBlazor.csproj @@ -1,19 +1,22 @@  - net8.0 + net9.0 enable enable - 8.0.0 + 9.0.0 + jmayer913 + jmayer913 + https://github.com/jmayer913/JMayer-Example-WebAssemblyBlazor - - - - + + + + diff --git a/TestProject/DataHelper.cs b/TestProject/DataHelper.cs index 964ecd4..a84dc7a 100644 --- a/TestProject/DataHelper.cs +++ b/TestProject/DataHelper.cs @@ -20,14 +20,13 @@ internal static class DataHelper public static async Task GetOrCreateAreaAssetAsync(HttpClient client, string areaName) { AssetDataLayer dataLayer = new(client); - List? assets = await dataLayer.GetAllAsync(); - if (assets != null) + if (assets is not null) { Asset? areaAsset = assets.FirstOrDefault(obj => obj.Name == areaName && obj.Type == AssetType.Area); - if (areaAsset != null) + if (areaAsset is not null) { return areaAsset; } @@ -46,14 +45,13 @@ internal static class DataHelper public static async Task GetOrCreatePartAsync(HttpClient client, string name) { PartDataLayer dataLayer = new(client); - List? parts = await dataLayer.GetAllAsync(); - if (parts != null) + if (parts is not null) { Part? part = parts.FirstOrDefault(obj => obj.Name == name); - if (part != null) + if (part is not null) { return part; } @@ -73,14 +71,13 @@ internal static class DataHelper public static async Task GetOrCreateStorageLocationAsync(HttpClient client, string locationName, long ownerID) { StorageLocationDataLayer dataLayer = new(client); - List? storageLocations = await dataLayer.GetAllAsync(); - if (storageLocations != null) + if (storageLocations is not null) { StorageLocation? foundStorageLocation = storageLocations.FirstOrDefault(obj => obj.LocationA == locationName); - if (foundStorageLocation != null) + if (foundStorageLocation is not null) { return foundStorageLocation; } diff --git a/TestProject/Test/WebRequest/Assets/AssetUnitTest.cs b/TestProject/Test/WebRequest/Assets/AssetUnitTest.cs index e97aca9..2993ed5 100644 --- a/TestProject/Test/WebRequest/Assets/AssetUnitTest.cs +++ b/TestProject/Test/WebRequest/Assets/AssetUnitTest.cs @@ -55,14 +55,13 @@ public async Task VerifyAddAsset(string name, string? description, AssetType ass long? parentID = null; - if (!string.IsNullOrEmpty(parentName)) + if (parentName is not null) { parentID = (await dataLayer.GetAllListViewAsync())?.FirstOrDefault(obj => obj.Name == parentName)?.Integer64ID; - if (parentID == null) + if (parentID is null) { Assert.Fail("The parent asset was not found."); - return; } } @@ -92,12 +91,7 @@ public async Task VerifyAddDuplicateAssetFailure() AssetDataLayer dataLayer = new(client); OperationResult operationResult = await dataLayer.CreateAsync(new Asset() { Name = "Duplicate Asset Test" }); - - if (!operationResult.IsSuccessStatusCode) - { - Assert.Fail("Failed to create the first asset."); - return; - } + Assert.True(operationResult.IsSuccessStatusCode, "Failed to create the first asset."); operationResult = await dataLayer.CreateAsync(new Asset() { Name = "Duplicate Asset Test" }); @@ -110,13 +104,10 @@ public async Task VerifyAddDuplicateAssetFailure() //A bad request status was returned. Assert.Equal(HttpStatusCode.BadRequest, operationResult.StatusCode); - //A validation error was returned. - Assert.NotNull(operationResult.ServerSideValidationResult); - Assert.Single(operationResult.ServerSideValidationResult.Errors); - //The correct error was returned. - Assert.Contains("name already exists", operationResult.ServerSideValidationResult.Errors[0].ErrorMessage); - Assert.Equal(nameof(Asset.Name), operationResult.ServerSideValidationResult.Errors[0].PropertyName); + Assert.Contains(operationResult.ValidationErrors, obj => obj.Key == nameof(Asset.Name)); + Assert.Single(operationResult.ValidationErrors[nameof(Asset.Name)]); + Assert.Contains("name already exists", operationResult.ValidationErrors[nameof(Asset.Name)][0]); } /// @@ -145,10 +136,10 @@ public async Task VerifyDeleteAsset() OperationResult operationResult = await dataLayer.CreateAsync(new Asset() { Name = "Delete Asset Test" }); - if (operationResult.DataObject is Asset asset) + if (operationResult.IsSuccessStatusCode && operationResult.DataObject is Asset asset) { operationResult = await dataLayer.DeleteAsync(asset); - Assert.True(operationResult.IsSuccessStatusCode); + Assert.True(operationResult.IsSuccessStatusCode, "The operation should have successed."); } else { @@ -169,23 +160,13 @@ public async Task VerifyDeleteAssetParentCascade() long preCount = await dataLayer.CountAsync(); OperationResult operationResult = await dataLayer.CreateAsync(new Asset() { Name = "Delete Parent Test" }); - if (operationResult.DataObject is Asset parentAsset) + if (operationResult.IsSuccessStatusCode && operationResult.DataObject is Asset parentAsset) { operationResult = await dataLayer.CreateAsync(new Asset() { Name = "Test Child 1", ParentID = parentAsset.Integer64ID }); - - if (!operationResult.IsSuccessStatusCode) - { - Assert.Fail("Failed to create the child asset."); - return; - } + Assert.True(operationResult.IsSuccessStatusCode, "Failed to create the child asset."); operationResult = await dataLayer.CreateAsync(new Asset() { Name = "Test Child 2", ParentID = parentAsset.Integer64ID }); - - if (!operationResult.IsSuccessStatusCode) - { - Assert.Fail("Failed to create the child asset."); - return; - } + Assert.True(operationResult.IsSuccessStatusCode, "Failed to create the child asset."); operationResult = await dataLayer.DeleteAsync(parentAsset); long postCount = await dataLayer.CountAsync(); @@ -197,7 +178,6 @@ public async Task VerifyDeleteAssetParentCascade() else { Assert.Fail("Failed to create the parent asset."); - return; } } @@ -278,7 +258,7 @@ public async Task VerifyGetSingleAssetWithId() OperationResult operationResult = await dataLayer.CreateAsync(new Asset() { Name = "Get Single Asset Test" }); - if (operationResult.DataObject is Asset createdAsset) + if (operationResult.IsSuccessStatusCode && operationResult.DataObject is Asset createdAsset) { Asset? asset = await dataLayer.GetSingleAsync(createdAsset.Integer64ID); Assert.NotNull(asset); @@ -311,7 +291,7 @@ public async Task VerifyGetSingleAssetWithId() [InlineData("Test Asset 6", "Test AL1-05", "Test AL1-05", "Conveyor", "Make", "Model", "Manufacturer", null, Priority.Medium, false)] [InlineData("Test Asset 7", "Test AL1-05-BSD", "Test AL1-05-BSD", "BSD", "Make", "Model", "Manufacturer", "Manufacturer Number", Priority.Medium, false)] [InlineData("Test Asset 8", "Test AL1-VSU", "Test AL1-VSU", "VSU", "Make", "Model", "Manufacturer", "Manufacturer Number", Priority.Medium, true)] - public async Task VerifyUpdateAsset(string originalName, string newName, string description, string? category, string? make, string? model, string? manufacturer, string? manufacturerNumber, Priority priority, bool online) + public async Task VerifyUpdateAsset(string originalName, string newName, string? description, string? category, string? make, string? model, string? manufacturer, string? manufacturerNumber, Priority priority, bool online) { HttpClient client = _factory.CreateClient(); AssetDataLayer dataLayer = new(client); @@ -360,7 +340,7 @@ public async Task VerifyUpdateAssetOldDataConflict() Name = "Old Data Asset Test", }); - if (operationResult.DataObject is Asset firstAsset) + if (operationResult.IsSuccessStatusCode && operationResult.DataObject is Asset firstAsset) { Asset secondAsset = new(firstAsset); @@ -368,12 +348,7 @@ public async Task VerifyUpdateAssetOldDataConflict() secondAsset.Category = "A Category"; operationResult = await dataLayer.UpdateAsync(secondAsset); - - if (!operationResult.IsSuccessStatusCode) - { - Assert.Fail("Failed to update the second asset."); - return; - } + Assert.True(operationResult.IsSuccessStatusCode, "Failed to update the second asset."); operationResult = await dataLayer.UpdateAsync(firstAsset); @@ -384,7 +359,6 @@ public async Task VerifyUpdateAssetOldDataConflict() else { Assert.Fail("Failed to create the asset."); - return; } } @@ -401,61 +375,42 @@ public async Task VerifyUpdateAssetTreeStructure() OperationResult operationResult = await dataLayer.CreateAsync(new Asset() { Name = "Root Asset Tree Structure Test" }); Asset? rootAsset = operationResult.DataObject as Asset; - if (rootAsset == null) + if (rootAsset is null) { Assert.Fail("Failed to create the root asset"); - return; } operationResult = await dataLayer.CreateAsync(new Asset() { Name = "Other Root Asset Tree Structure Test" }); Asset? otherRootAsset = operationResult.DataObject as Asset; - if (otherRootAsset == null) + if (otherRootAsset is null) { Assert.Fail("Failed to create the other root asset"); - return; } operationResult = await dataLayer.CreateAsync(new Asset() { Name = "Middle Asset Tree Structure Test", ParentID = rootAsset.Integer64ID }); Asset? middleAsset = operationResult.DataObject as Asset; - if (middleAsset == null) + if (middleAsset is null) { Assert.Fail("Failed to create the middle asset."); - return; } operationResult = await dataLayer.CreateAsync(new Asset() { Name = "Leaf Asset Tree Structure Test 1", ParentID = middleAsset.Integer64ID }); - - if (!operationResult.IsSuccessStatusCode) - { - Assert.Fail("Failed to create the first leaf asset."); - return; - } + Assert.True(operationResult.IsSuccessStatusCode, "Failed to create the first leaf asset."); operationResult = await dataLayer.CreateAsync(new Asset() { Name = "Leaf Asset Tree Structure Test 2", ParentID = middleAsset.Integer64ID }); - - if (!operationResult.IsSuccessStatusCode) - { - Assert.Fail("Failed to create the second leaf asset."); - return; - } + Assert.True(operationResult.IsSuccessStatusCode, "Failed to create the second leaf asset."); middleAsset.ParentID = otherRootAsset.Integer64ID; operationResult = await dataLayer.UpdateAsync(middleAsset); - - if (!operationResult.IsSuccessStatusCode) - { - Assert.Fail("Failed to update the parent of the middle asset."); - return; - } + Assert.True(operationResult.IsSuccessStatusCode, "Failed to update the parent of the middle asset."); List? allAssets = await dataLayer.GetAllAsync(); - if (allAssets == null) + if (allAssets is null) { Assert.Fail("Failed to retrieve the assets."); - return; } List testTreeAssets = allAssets.Where(obj => obj.Integer64ID == middleAsset.Integer64ID || obj.ParentID == middleAsset.Integer64ID).ToList(); @@ -473,16 +428,11 @@ public async Task VerifyUpdateDuplicateAssetFailure() AssetDataLayer dataLayer = new(client); OperationResult operationResult = await dataLayer.CreateAsync(new Asset() { Name = "Duplicate Asset Test 1" }); - - if (!operationResult.IsSuccessStatusCode) - { - Assert.Fail("Failed to create the first asset."); - return; - } + Assert.True(operationResult.IsSuccessStatusCode, "Failed to create the first asset."); operationResult = await dataLayer.CreateAsync(new Asset() { Name = "Duplicate Asset Test 2" }); - if (operationResult.DataObject is Asset asset) + if (operationResult.IsSuccessStatusCode && operationResult.DataObject is Asset asset) { asset.Name = "Duplicate Asset Test 1"; operationResult = await dataLayer.UpdateAsync(asset); @@ -496,13 +446,10 @@ public async Task VerifyUpdateDuplicateAssetFailure() //A bad request status was returned. Assert.Equal(HttpStatusCode.BadRequest, operationResult.StatusCode); - //A validation error was returned. - Assert.NotNull(operationResult.ServerSideValidationResult); - Assert.Single(operationResult.ServerSideValidationResult.Errors); - //The correct error was returned. - Assert.Contains("name already exists", operationResult.ServerSideValidationResult.Errors[0].ErrorMessage); - Assert.Equal(nameof(Asset.Name), operationResult.ServerSideValidationResult.Errors[0].PropertyName); + Assert.Contains(operationResult.ValidationErrors, obj => obj.Key == nameof(Asset.Name)); + Assert.Single(operationResult.ValidationErrors[nameof(Asset.Name)]); + Assert.Contains("name already exists", operationResult.ValidationErrors[nameof(Asset.Name)][0]); } else { @@ -523,55 +470,37 @@ public async Task VerifyUpdateRootAssetName() OperationResult operationResult = await dataLayer.CreateAsync(new Asset() { Name = "Root Asset Rename Test" }); Asset? rootAsset = operationResult.DataObject as Asset; - if (rootAsset == null) + if (rootAsset is null) { Assert.Fail("Failed to create the root asset"); - return; } operationResult = await dataLayer.CreateAsync(new Asset() { Name = "Middle Asset Rename Test", ParentID = rootAsset.Integer64ID }); Asset? middleAsset = operationResult.DataObject as Asset; - if (middleAsset == null) + if (middleAsset is null) { Assert.Fail("Failed to create the middle asset."); - return; } operationResult = await dataLayer.CreateAsync(new Asset() { Name = "Leaf Asset Rename Test 1", ParentID = middleAsset.Integer64ID }); - - if (!operationResult.IsSuccessStatusCode) - { - Assert.Fail("Failed to create the first leaf asset."); - return; - } + Assert.True(operationResult.IsSuccessStatusCode, "Failed to create the first leaf asset."); operationResult = await dataLayer.CreateAsync(new Asset() { Name = "Leaf Asset Rename Test 2", ParentID = middleAsset.Integer64ID }); - - if (!operationResult.IsSuccessStatusCode) - { - Assert.Fail("Failed to create the second leaf asset."); - return; - } + Assert.True(operationResult.IsSuccessStatusCode, "Failed to create the second leaf asset."); rootAsset.Name = "New Root Asset Rename Test"; operationResult = await dataLayer.UpdateAsync(rootAsset); - - if (!operationResult.IsSuccessStatusCode) - { - Assert.Fail("Failed to rename of the root asset."); - return; - } + Assert.True(operationResult.IsSuccessStatusCode, "Failed to rename of the root asset."); List? allAssets = await dataLayer.GetAllAsync(); - if (allAssets == null) + if (allAssets is null) { Assert.Fail("Failed to retrieve the assets."); - return; } - List testTreeAssets = allAssets.Where(obj => obj.Integer64ID == middleAsset.Integer64ID || obj.ParentID == middleAsset.Integer64ID).ToList(); - Assert.All(testTreeAssets, asset => Assert.True(asset.ParentPath != null && asset.ParentPath.Contains(rootAsset.Name))); + List testTreeAssets = [.. allAssets.Where(obj => obj.Integer64ID == middleAsset.Integer64ID || obj.ParentID == middleAsset.Integer64ID)]; + Assert.All(testTreeAssets, asset => Assert.True(asset.ParentPath is not null && asset.ParentPath.Contains(rootAsset.Name))); } } diff --git a/TestProject/Test/WebRequest/Assets/StorageLocationUnitTest.cs b/TestProject/Test/WebRequest/Assets/StorageLocationUnitTest.cs index 0bb3bf6..0fda7c4 100644 --- a/TestProject/Test/WebRequest/Assets/StorageLocationUnitTest.cs +++ b/TestProject/Test/WebRequest/Assets/StorageLocationUnitTest.cs @@ -46,10 +46,9 @@ public async Task VerifyAddDuplicateStorageLocationFailure(string locationA, str Asset? areaAsset = await DataHelper.GetOrCreateAreaAssetAsync(client, Constants.TestAreaAsset); - if (areaAsset == null) + if (areaAsset is null) { Assert.Fail("Failed to retrieve or create the area asset."); - return; } StorageLocation storageLocation = new() @@ -61,12 +60,7 @@ public async Task VerifyAddDuplicateStorageLocationFailure(string locationA, str }; storageLocation.Name = storageLocation.FriendlyName; OperationResult operationResult = await dataLayer.CreateAsync(storageLocation); - - if (!operationResult.IsSuccessStatusCode) - { - Assert.Fail("Failed to create the first storage location."); - return; - } + Assert.True(operationResult.IsSuccessStatusCode, "Failed to create the first storage location."); storageLocation = new() { @@ -87,13 +81,10 @@ public async Task VerifyAddDuplicateStorageLocationFailure(string locationA, str //A bad request status was returned. Assert.Equal(HttpStatusCode.BadRequest, operationResult.StatusCode); - //A validation error was returned. - Assert.NotNull(operationResult.ServerSideValidationResult); - Assert.Single(operationResult.ServerSideValidationResult.Errors); - //The correct error was returned. - Assert.Contains("location already exists", operationResult.ServerSideValidationResult.Errors[0].ErrorMessage); - Assert.Equal(nameof(StorageLocation.LocationA), operationResult.ServerSideValidationResult.Errors[0].PropertyName); + Assert.Contains(operationResult.ValidationErrors, obj => obj.Key == nameof(StorageLocation.LocationA)); + Assert.Single(operationResult.ValidationErrors[nameof(StorageLocation.LocationA)]); + Assert.Contains("location already exists", operationResult.ValidationErrors[nameof(StorageLocation.LocationA)][0]); } /// @@ -114,10 +105,9 @@ public async Task VerifyAddStorageLocation(string locationA, string locationB, s Asset? areaAsset = await DataHelper.GetOrCreateAreaAssetAsync(client, Constants.TestAreaAsset); - if (areaAsset == null) + if (areaAsset is null) { Assert.Fail("Failed to retrieve or create the area asset."); - return; } StorageLocation storageLocation = new() @@ -156,13 +146,10 @@ public async Task VerifyAddStorageLocationDependenciesNotExists() //A bad request status was returned. Assert.Equal(HttpStatusCode.BadRequest, operationResult.StatusCode); - //A validation error was returned. - Assert.NotNull(operationResult.ServerSideValidationResult); - Assert.Single(operationResult.ServerSideValidationResult.Errors); - //The correct error was returned. - Assert.Contains("asset was not found", operationResult.ServerSideValidationResult.Errors[0].ErrorMessage); - Assert.Equal(nameof(StorageLocation.OwnerInteger64ID), operationResult.ServerSideValidationResult.Errors[0].PropertyName); + Assert.Contains(operationResult.ValidationErrors, obj => obj.Key == nameof(StorageLocation.OwnerInteger64ID)); + Assert.Single(operationResult.ValidationErrors[nameof(StorageLocation.OwnerInteger64ID)]); + Assert.Contains("asset was not found", operationResult.ValidationErrors[nameof(StorageLocation.OwnerInteger64ID)][0]); } /// @@ -191,35 +178,19 @@ public async Task VerifyDeleteAreaAssetCascade() Asset? areaAsset = await DataHelper.GetOrCreateAreaAssetAsync(client, "Cascade Area Asset-Storage Location Delete Test"); - if (areaAsset == null) + if (areaAsset is null) { Assert.Fail("Failed to retrieve or create the area asset."); - return; } OperationResult operationResult = await dataLayer.CreateAsync(new StorageLocation() { LocationA = "Cascade Area Asset-Storage Location Delete Test 1", Name = "Cascade Storage Location Delete Test 1", OwnerInteger64ID = areaAsset.Integer64ID }); - - if (!operationResult.IsSuccessStatusCode) - { - Assert.Fail("Failed to create the storage location."); - return; - } + Assert.True(operationResult.IsSuccessStatusCode, "Failed to create the first storage location."); operationResult = await dataLayer.CreateAsync(new StorageLocation() { LocationA = "Cascade Area Asset-Storage Location Delete Test 2", Name = "Cascade Storage Location Delete Test 2", OwnerInteger64ID = areaAsset.Integer64ID }); - - if (!operationResult.IsSuccessStatusCode) - { - Assert.Fail("Failed to create the storage location."); - return; - } + Assert.True(operationResult.IsSuccessStatusCode, "Failed to create the second storage location."); operationResult = await dataLayer.CreateAsync(new StorageLocation() { LocationA = "Cascade Area Asset-Storage Location Delete Test 3", Name = "Cascade Storage Location Delete Test 3", OwnerInteger64ID = areaAsset.Integer64ID }); - - if (!operationResult.IsSuccessStatusCode) - { - Assert.Fail("Failed to create the storage location."); - return; - } + Assert.True(operationResult.IsSuccessStatusCode, "Failed to create the third storage location."); await new AssetDataLayer(client).DeleteAsync(areaAsset); @@ -241,15 +212,14 @@ public async Task VerifyDeleteStorageLocation() Asset? areaAsset = await DataHelper.GetOrCreateAreaAssetAsync(client, Constants.TestAreaAsset); - if (areaAsset == null) + if (areaAsset is null) { Assert.Fail("Failed to retrieve or create the area asset."); - return; } OperationResult operationResult = await dataLayer.CreateAsync(new StorageLocation() { LocationA = "Delete Storage Location Test", Name = "Test", OwnerInteger64ID = areaAsset.Integer64ID }); - if (operationResult.DataObject is StorageLocation storageLocation) + if (operationResult.IsSuccessStatusCode && operationResult.DataObject is StorageLocation storageLocation) { operationResult = await dataLayer.DeleteAsync(storageLocation); Assert.True(operationResult.IsSuccessStatusCode, "The operation should have been successful."); @@ -289,10 +259,9 @@ public async Task VerifyGetAllListViewStorageLocationsWithOwnerId() Asset? areaAsset = await DataHelper.GetOrCreateAreaAssetAsync(client, BHSExampleBuilder.MainPartStorageAreaAssetName); - if (areaAsset == null) + if (areaAsset is null) { Assert.Fail("Failed to find the area asset"); - return; } List? storageLocations = await dataLayer.GetAllListViewAsync(areaAsset.Integer64ID); @@ -331,10 +300,9 @@ public async Task VerifyGetAllStorageLocationsWithOwnerId() Asset? areaAsset = await DataHelper.GetOrCreateAreaAssetAsync(client, BHSExampleBuilder.MainPartStorageAreaAssetName); - if (areaAsset == null) + if (areaAsset is null) { Assert.Fail("Failed to find the area asset"); - return; } List? storageLocations = await dataLayer.GetAllAsync(areaAsset.Integer64ID); @@ -370,15 +338,14 @@ public async Task VerifyGetSingleStorageLocationWithIdA() Asset? areaAsset = await DataHelper.GetOrCreateAreaAssetAsync(client, Constants.TestAreaAsset); - if (areaAsset == null) + if (areaAsset is null) { Assert.Fail("Failed to retrieve or create the area asset."); - return; } OperationResult operationResult = await dataLayer.CreateAsync(new StorageLocation() { LocationA = "Get Single Storage Location Test", Name = "Test", OwnerInteger64ID = areaAsset.Integer64ID }); - if (operationResult.DataObject is StorageLocation createdStorageLocation) + if (operationResult.IsSuccessStatusCode && operationResult.DataObject is StorageLocation createdStorageLocation) { StorageLocation? fetchedStorageLocation = await dataLayer.GetSingleAsync(createdStorageLocation.Integer64ID); Assert.NotNull(fetchedStorageLocation); @@ -407,10 +374,9 @@ public async Task VerifyUpdateDuplicateStorageLocationFailure(string locationA, Asset? areaAsset = await DataHelper.GetOrCreateAreaAssetAsync(client, Constants.TestAreaAsset); - if (areaAsset == null) + if (areaAsset is null) { Assert.Fail("Failed to retrieve or create the area asset."); - return; } StorageLocation firstStorageLocation = new() @@ -422,12 +388,7 @@ public async Task VerifyUpdateDuplicateStorageLocationFailure(string locationA, }; firstStorageLocation.Name = firstStorageLocation.FriendlyName; OperationResult operationResult = await dataLayer.CreateAsync(firstStorageLocation); - - if (!operationResult.IsSuccessStatusCode) - { - Assert.Fail("Failed to create the first storage location."); - return; - } + Assert.True(operationResult.IsSuccessStatusCode, "Failed to create the first storage location."); StorageLocation secondStorageLocation = new() { @@ -455,18 +416,14 @@ public async Task VerifyUpdateDuplicateStorageLocationFailure(string locationA, //A bad request status was returned. Assert.Equal(HttpStatusCode.BadRequest, operationResult.StatusCode); - //A validation error was returned. - Assert.NotNull(operationResult.ServerSideValidationResult); - Assert.Single(operationResult.ServerSideValidationResult.Errors); - //The correct error was returned. - Assert.Contains("location already exists", operationResult.ServerSideValidationResult.Errors[0].ErrorMessage); - Assert.Equal(nameof(StorageLocation.LocationA), operationResult.ServerSideValidationResult.Errors[0].PropertyName); + Assert.Contains(operationResult.ValidationErrors, obj => obj.Key == nameof(StorageLocation.LocationA)); + Assert.Single(operationResult.ValidationErrors[nameof(StorageLocation.LocationA)]); + Assert.Contains("location already exists", operationResult.ValidationErrors[nameof(StorageLocation.LocationA)][0]); } else { Assert.Fail("Failed to create the second storage location."); - return; } } @@ -488,10 +445,9 @@ public async Task VerifyUpdateStorageLocation(string originalLocationA, string l Asset? areaAsset = await DataHelper.GetOrCreateAreaAssetAsync(client, Constants.TestAreaAsset); - if (areaAsset == null) + if (areaAsset is null) { Assert.Fail("Failed to retrieve or create the area asset."); - return; } StorageLocation originalStorageLocation = new() @@ -534,10 +490,9 @@ public async Task VerifyUpdateStorageLocationDependenciesNotExists() Asset? areaAsset = await DataHelper.GetOrCreateAreaAssetAsync(client, Constants.TestAreaAsset); - if (areaAsset == null) + if (areaAsset is null) { Assert.Fail("Failed to retrieve or create the area asset."); - return; } StorageLocation? storageLocation = new() @@ -549,10 +504,9 @@ public async Task VerifyUpdateStorageLocationDependenciesNotExists() OperationResult operationResult = await dataLayer.CreateAsync(storageLocation); storageLocation = operationResult.DataObject as StorageLocation; - if (storageLocation == null) + if (storageLocation is null) { Assert.Fail("Failed to create the storage location."); - return; } storageLocation.OwnerInteger64ID = 0; @@ -567,13 +521,10 @@ public async Task VerifyUpdateStorageLocationDependenciesNotExists() //A bad request status was returned. Assert.Equal(HttpStatusCode.BadRequest, operationResult.StatusCode); - //A validation error was returned. - Assert.NotNull(operationResult.ServerSideValidationResult); - Assert.Single(operationResult.ServerSideValidationResult.Errors); - //The correct error was returned. - Assert.Contains("asset was not found", operationResult.ServerSideValidationResult.Errors[0].ErrorMessage); - Assert.Equal(nameof(StorageLocation.OwnerInteger64ID), operationResult.ServerSideValidationResult.Errors[0].PropertyName); + Assert.Contains(operationResult.ValidationErrors, obj => obj.Key == nameof(StorageLocation.OwnerInteger64ID)); + Assert.Single(operationResult.ValidationErrors[nameof(StorageLocation.OwnerInteger64ID)]); + Assert.Contains("asset was not found", operationResult.ValidationErrors[nameof(StorageLocation.OwnerInteger64ID)][0]); } /// @@ -588,10 +539,9 @@ public async Task VerifyUpdateStorageLocationOldDataConflict() Asset? areaAsset = await DataHelper.GetOrCreateAreaAssetAsync(client, Constants.TestAreaAsset); - if (areaAsset == null) + if (areaAsset is null) { Assert.Fail("Failed to retrieve or create the area asset."); - return; } OperationResult operationResult = await dataLayer.CreateAsync(new StorageLocation() @@ -601,7 +551,7 @@ public async Task VerifyUpdateStorageLocationOldDataConflict() OwnerInteger64ID = areaAsset.Integer64ID, }); - if (operationResult.DataObject is StorageLocation firstStorageLocation) + if (operationResult.IsSuccessStatusCode && operationResult.DataObject is StorageLocation firstStorageLocation) { StorageLocation secondStorageLocation = new(firstStorageLocation); @@ -609,12 +559,7 @@ public async Task VerifyUpdateStorageLocationOldDataConflict() secondStorageLocation.LocationB = "Old Data Storage Location Test 2"; operationResult = await dataLayer.UpdateAsync(secondStorageLocation); - - if (!operationResult.IsSuccessStatusCode) - { - Assert.Fail("Failed to update the second storage location."); - return; - } + Assert.True(operationResult.IsSuccessStatusCode, "Failed to update the second storage location."); operationResult = await dataLayer.UpdateAsync(firstStorageLocation); @@ -625,7 +570,6 @@ public async Task VerifyUpdateStorageLocationOldDataConflict() else { Assert.Fail("Failed to create the storage location."); - return; } } } diff --git a/TestProject/Test/WebRequest/Parts/PartUnitTest.cs b/TestProject/Test/WebRequest/Parts/PartUnitTest.cs index c58e7c0..d145dfc 100644 --- a/TestProject/Test/WebRequest/Parts/PartUnitTest.cs +++ b/TestProject/Test/WebRequest/Parts/PartUnitTest.cs @@ -38,12 +38,7 @@ public async Task VerifyAddDuplicatePartFailure() PartDataLayer dataLayer = new(client); OperationResult operationResult = await dataLayer.CreateAsync(new Part() { Name = "Add Duplicate Part Test" }); - - if (!operationResult.IsSuccessStatusCode) - { - Assert.Fail("Failed to create the first part."); - return; - } + Assert.True(operationResult.IsSuccessStatusCode, "Failed to create the first part."); operationResult = await dataLayer.CreateAsync(new Part() { Name = "Add Duplicate Part Test" }); @@ -56,13 +51,10 @@ public async Task VerifyAddDuplicatePartFailure() //A bad request status was returned. Assert.Equal(HttpStatusCode.BadRequest, operationResult.StatusCode); - //A validation error was returned. - Assert.NotNull(operationResult.ServerSideValidationResult); - Assert.Single(operationResult.ServerSideValidationResult.Errors); - //The correct error was returned. - Assert.Contains("name already exists", operationResult.ServerSideValidationResult.Errors[0].ErrorMessage); - Assert.Equal(nameof(Part.Name), operationResult.ServerSideValidationResult.Errors[0].PropertyName); + Assert.Contains(operationResult.ValidationErrors, obj => obj.Key == nameof(Part.Name)); + Assert.Single(operationResult.ValidationErrors[nameof(Part.Name)]); + Assert.Contains("name already exists", operationResult.ValidationErrors[nameof(Part.Name)][0]); } /// @@ -120,7 +112,7 @@ public async Task VerifyDeletePart() OperationResult operationResult = await dataLayer.CreateAsync(new Part() { Name = "Delete Part Test" }); - if (operationResult.DataObject is Part part) + if (operationResult.IsSuccessStatusCode && operationResult.DataObject is Part part) { operationResult = await dataLayer.DeleteAsync(part); Assert.True(operationResult.IsSuccessStatusCode); @@ -208,7 +200,7 @@ public async Task VerifyGetSinglePartWithId() OperationResult operationResult = await dataLayer.CreateAsync(new Part() { Name = "Get Single Part Test" }); - if (operationResult.DataObject is Part createdPart) + if (operationResult.IsSuccessStatusCode && operationResult.DataObject is Part createdPart) { Part? fetchedPart = await dataLayer.GetSingleAsync(createdPart.Integer64ID); Assert.NotNull(fetchedPart); @@ -230,16 +222,11 @@ public async Task VerifyUpdateDuplicatePartFailure() PartDataLayer dataLayer = new(client); OperationResult operationResult = await dataLayer.CreateAsync(new Part() { Name = "Add Duplicate Part Test 1" }); - - if (!operationResult.IsSuccessStatusCode) - { - Assert.Fail("Failed to create the first part."); - return; - } + Assert.True(operationResult.IsSuccessStatusCode, "Failed to create the first part."); operationResult = await dataLayer.CreateAsync(new Part() { Name = "Add Duplicate Part Test 2" }); - if (operationResult.DataObject is Part part) + if (operationResult.IsSuccessStatusCode && operationResult.DataObject is Part part) { part.Name = "Add Duplicate Part Test 1"; operationResult = await dataLayer.UpdateAsync(part); @@ -253,18 +240,14 @@ public async Task VerifyUpdateDuplicatePartFailure() //A bad request status was returned. Assert.Equal(HttpStatusCode.BadRequest, operationResult.StatusCode); - //A validation error was returned. - Assert.NotNull(operationResult.ServerSideValidationResult); - Assert.Single(operationResult.ServerSideValidationResult.Errors); - //The correct error was returned. - Assert.Contains("name already exists", operationResult.ServerSideValidationResult.Errors[0].ErrorMessage); - Assert.Equal(nameof(Part.Name), operationResult.ServerSideValidationResult.Errors[0].PropertyName); + Assert.Contains(operationResult.ValidationErrors, obj => obj.Key == nameof(Part.Name)); + Assert.Single(operationResult.ValidationErrors[nameof(Part.Name)]); + Assert.Contains("name already exists", operationResult.ValidationErrors[nameof(Part.Name)][0]); } else { Assert.Fail("Failed to create the second part."); - return; } } @@ -337,7 +320,7 @@ public async Task VerifyUpdatePartOldDataConflict() Name = "Old Data Part Test", }); - if (operationResult.DataObject is Part firstPart) + if (operationResult.IsSuccessStatusCode && operationResult.DataObject is Part firstPart) { Part secondPart = new(firstPart); @@ -345,12 +328,7 @@ public async Task VerifyUpdatePartOldDataConflict() secondPart.Obsolete = true; operationResult = await dataLayer.UpdateAsync(secondPart); - - if (!operationResult.IsSuccessStatusCode) - { - Assert.Fail("Failed to update the second part."); - return; - } + Assert.True(operationResult.IsSuccessStatusCode, "Failed to update the second part."); operationResult = await dataLayer.UpdateAsync(firstPart); @@ -361,7 +339,6 @@ public async Task VerifyUpdatePartOldDataConflict() else { Assert.Fail("Failed to create the part."); - return; } } } diff --git a/TestProject/Test/WebRequest/Parts/StockUnitTest.cs b/TestProject/Test/WebRequest/Parts/StockUnitTest.cs index f24ad45..5a9d6d7 100644 --- a/TestProject/Test/WebRequest/Parts/StockUnitTest.cs +++ b/TestProject/Test/WebRequest/Parts/StockUnitTest.cs @@ -18,6 +18,11 @@ namespace TestProject.Test.WebRequest.Parts; /// public class StockUnitTest : IClassFixture> { + /// + /// The constant for the bad storage location ID. + /// + private const int BadStorageLocationID = 99; + /// /// The factory for the web application. /// @@ -41,35 +46,27 @@ public async Task VerifyAddDuplicateStockFailure() Asset? areaAsset = await DataHelper.GetOrCreateAreaAssetAsync(client, Constants.TestAreaAsset); - if (areaAsset == null) + if (areaAsset is null) { Assert.Fail("Failed to retrieve or create the area asset."); - return; } StorageLocation? storageLocation = await DataHelper.GetOrCreateStorageLocationAsync(client, Constants.TestStorageLocation, areaAsset.Integer64ID); - if (storageLocation == null) + if (storageLocation is null) { Assert.Fail("Failed to retrieve or create the storage location."); - return; } Part? part = await DataHelper.GetOrCreatePartAsync(client, "Duplicate Stock Test"); - if (part == null) + if (part is null) { Assert.Fail("Failed to retrieve or create the part."); - return; } OperationResult operationResult = await dataLayer.CreateAsync(new Stock() { Amount = 0, Name = "Duplicate Stock Test", OwnerInteger64ID = part.Integer64ID, StorageLocationID = storageLocation.Integer64ID, StorageLocationName = storageLocation.FriendlyName }); - - if (!operationResult.IsSuccessStatusCode) - { - Assert.Fail("Failed to create the first stock."); - return; - } + Assert.True(operationResult.IsSuccessStatusCode, "Failed to create the first stock."); operationResult = await dataLayer.CreateAsync(new Stock() { Amount = 0, Name = "Duplicate Stock Test", OwnerInteger64ID = part.Integer64ID, StorageLocationID = storageLocation.Integer64ID, StorageLocationName = storageLocation.FriendlyName }); @@ -82,13 +79,10 @@ public async Task VerifyAddDuplicateStockFailure() //A bad request status was returned. Assert.Equal(HttpStatusCode.BadRequest, operationResult.StatusCode); - //A validation error was returned. - Assert.NotNull(operationResult.ServerSideValidationResult); - Assert.Single(operationResult.ServerSideValidationResult.Errors); - //The correct error was returned. - Assert.Contains("stock location already exists", operationResult.ServerSideValidationResult.Errors[0].ErrorMessage); - Assert.Equal(nameof(Stock.StorageLocationID), operationResult.ServerSideValidationResult.Errors[0].PropertyName); + Assert.Contains(operationResult.ValidationErrors, obj => obj.Key == nameof(Stock.StorageLocationID)); + Assert.Single(operationResult.ValidationErrors[nameof(Stock.StorageLocationID)]); + Assert.Contains("stock location already exists", operationResult.ValidationErrors[nameof(Stock.StorageLocationID)][0]); } /// @@ -109,26 +103,23 @@ public async Task VerifyAddStock(string locationName, string partName, decimal a Asset? areaAsset = await DataHelper.GetOrCreateAreaAssetAsync(client, Constants.TestAreaAsset); - if (areaAsset == null) + if (areaAsset is null) { Assert.Fail("Failed to retrieve or create the area asset."); - return; } StorageLocation? storageLocation = await DataHelper.GetOrCreateStorageLocationAsync(client, locationName, areaAsset.Integer64ID); - if (storageLocation == null) + if (storageLocation is null) { Assert.Fail("Failed to retrieve or create the storage location."); - return; } Part? part = await DataHelper.GetOrCreatePartAsync(client, partName); - if (part == null) + if (part is null) { Assert.Fail("Failed to retrieve or create the part."); - return; } Stock partStock = new() @@ -156,7 +147,7 @@ public async Task VerifyAddStockDependenciesNotExists() HttpClient client = _factory.CreateClient(); StockDataLayer dataLayer = new(client); - OperationResult operationResult = await dataLayer.CreateAsync(new Stock() { Amount = 0, Name = "Add Dependencies Not Exists Stock Test", OwnerInteger64ID = 0, StorageLocationID = 0 }); + OperationResult operationResult = await dataLayer.CreateAsync(new Stock() { Amount = 0, Name = "Add Dependencies Not Exists Stock Test", OwnerInteger64ID = 0, StorageLocationID = BadStorageLocationID }); //The operation must have failed. Assert.False(operationResult.IsSuccessStatusCode, "The operation should have failed."); @@ -167,15 +158,14 @@ public async Task VerifyAddStockDependenciesNotExists() //A bad request status was returned. Assert.Equal(HttpStatusCode.BadRequest, operationResult.StatusCode); - //A validation error was returned. - Assert.NotNull(operationResult.ServerSideValidationResult); - Assert.Equal(2, operationResult.ServerSideValidationResult.Errors.Count); - //The correct error was returned. - Assert.Contains("part was not found", operationResult.ServerSideValidationResult.Errors[0].ErrorMessage); - Assert.Equal(nameof(Stock.OwnerInteger64ID), operationResult.ServerSideValidationResult.Errors[0].PropertyName); - Assert.Contains("storage location was not found", operationResult.ServerSideValidationResult.Errors[1].ErrorMessage); - Assert.Equal(nameof(Stock.StorageLocationID), operationResult.ServerSideValidationResult.Errors[1].PropertyName); + Assert.Contains(operationResult.ValidationErrors, obj => obj.Key == nameof(Stock.OwnerInteger64ID)); + Assert.Single(operationResult.ValidationErrors[nameof(Stock.OwnerInteger64ID)]); + Assert.Contains("part was not found", operationResult.ValidationErrors[nameof(Stock.OwnerInteger64ID)][0]); + + Assert.Contains(operationResult.ValidationErrors, obj => obj.Key == nameof(Stock.StorageLocationID)); + Assert.Single(operationResult.ValidationErrors[nameof(Stock.StorageLocationID)]); + Assert.Contains("storage location was not found", operationResult.ValidationErrors[nameof(Stock.StorageLocationID)][0]); } /// @@ -204,37 +194,30 @@ public async Task VerifyDeleteAreaAssetCascade() Asset? areaAsset = await DataHelper.GetOrCreateAreaAssetAsync(client, "Cascade Asset-Stock Delete Test"); - if (areaAsset == null) + if (areaAsset is null) { Assert.Fail("Failed to retrieve or create the area asset."); - return; } StorageLocation? storageLocation = await DataHelper.GetOrCreateStorageLocationAsync(client, "Cascade Asset-Stock Delete Test", areaAsset.Integer64ID); - if (storageLocation == null) + if (storageLocation is null) { Assert.Fail("Failed to retrieve or create the storage location."); - return; } Part? part = await DataHelper.GetOrCreatePartAsync(client, Constants.TestPart); - if (part == null) + if (part is null) { Assert.Fail("Failed to retrieve or create the part."); - return; } OperationResult operationResult = await dataLayer.CreateAsync(new Stock() { Amount = 0, Name = "Cascade Asset-Stock Delete Test", OwnerInteger64ID = part.Integer64ID, StorageLocationID = storageLocation.Integer64ID, StorageLocationName = storageLocation.FriendlyName }); + Assert.True(operationResult.IsSuccessStatusCode, "Failed to create the stock."); - if (!operationResult.IsSuccessStatusCode) - { - Assert.Fail("Failed to create the stock."); - return; - } - - await new AssetDataLayer(client).DeleteAsync(areaAsset); + operationResult = await new AssetDataLayer(client).DeleteAsync(areaAsset); + Assert.True(operationResult.IsSuccessStatusCode, "Failed to delete the area asset."); List? stocks = await dataLayer.GetAllAsync(areaAsset.Integer64ID); @@ -255,37 +238,30 @@ public async Task VerifyDeletePartCascade() Asset? areaAsset = await DataHelper.GetOrCreateAreaAssetAsync(client, Constants.TestAreaAsset); - if (areaAsset == null) + if (areaAsset is null) { Assert.Fail("Failed to retrieve or create the area asset."); - return; } StorageLocation? storageLocation = await DataHelper.GetOrCreateStorageLocationAsync(client, Constants.TestStorageLocation, areaAsset.Integer64ID); - if (storageLocation == null) + if (storageLocation is null) { Assert.Fail("Failed to retrieve or create the storage location."); - return; } Part? part = await DataHelper.GetOrCreatePartAsync(client, "Cascade Part-Stock Delete Test"); - if (part == null) + if (part is null) { Assert.Fail("Failed to retrieve or create the part."); - return; } OperationResult operationResult = await dataLayer.CreateAsync(new Stock() { Amount = 0, Name = "Cascade Part-Stock Delete Test", OwnerInteger64ID = part.Integer64ID, StorageLocationID = storageLocation.Integer64ID, StorageLocationName = storageLocation.FriendlyName }); + Assert.True(operationResult.IsSuccessStatusCode, "Failed to create the stock."); - if (!operationResult.IsSuccessStatusCode) - { - Assert.Fail("Failed to create the stock."); - return; - } - - await new PartDataLayer(client).DeleteAsync(part); + operationResult = await new PartDataLayer(client).DeleteAsync(part); + Assert.True(operationResult.IsSuccessStatusCode, "Failed to delete the part."); List? stocks = await dataLayer.GetAllAsync(areaAsset.Integer64ID); @@ -306,31 +282,28 @@ public async Task VerifyDeleteStock() Asset? areaAsset = await DataHelper.GetOrCreateAreaAssetAsync(client, Constants.TestAreaAsset); - if (areaAsset == null) + if (areaAsset is null) { Assert.Fail("Failed to retrieve or create the area asset."); - return; } StorageLocation? storageLocation = await DataHelper.GetOrCreateStorageLocationAsync(client, Constants.TestStorageLocation, areaAsset.Integer64ID); - if (storageLocation == null) + if (storageLocation is null) { Assert.Fail("Failed to retrieve or create the storage location."); - return; } Part? part = await DataHelper.GetOrCreatePartAsync(client, "Delete Stock Test"); - if (part == null) + if (part is null) { Assert.Fail("Failed to retrieve or create the part."); - return; } OperationResult operationResult = await dataLayer.CreateAsync(new Stock() { Amount = 0, Name = "Delete Stock Test", OwnerInteger64ID = part.Integer64ID, StorageLocationID = storageLocation.Integer64ID, StorageLocationName = storageLocation.FriendlyName }); - if (operationResult.DataObject is Stock stock) + if (operationResult.IsSuccessStatusCode && operationResult.DataObject is Stock stock) { operationResult = await dataLayer.DeleteAsync(stock); Assert.True(operationResult.IsSuccessStatusCode); @@ -353,37 +326,30 @@ public async Task VerifyDeleteStorageLocationCascade() Asset? areaAsset = await DataHelper.GetOrCreateAreaAssetAsync(client, Constants.TestAreaAsset); - if (areaAsset == null) + if (areaAsset is null) { Assert.Fail("Failed to retrieve or create the area asset."); - return; } StorageLocation? storageLocation = await DataHelper.GetOrCreateStorageLocationAsync(client, "Cascade Storage Location-Stock Delete Test", areaAsset.Integer64ID); - if (storageLocation == null) + if (storageLocation is null) { Assert.Fail("Failed to retrieve or create the storage location."); - return; } Part? part = await DataHelper.GetOrCreatePartAsync(client, Constants.TestPart); - if (part == null) + if (part is null) { Assert.Fail("Failed to retrieve or create the part."); - return; } OperationResult operationResult = await dataLayer.CreateAsync(new Stock() { Amount = 0, Name = "Cascade Storage Location-Stock Delete Test", OwnerInteger64ID = part.Integer64ID, StorageLocationID = storageLocation.Integer64ID, StorageLocationName = storageLocation.FriendlyName }); + Assert.True(operationResult.IsSuccessStatusCode, "Failed to create the stock."); - if (!operationResult.IsSuccessStatusCode) - { - Assert.Fail("Failed to create the stock."); - return; - } - - await new StorageLocationDataLayer(client).DeleteAsync(storageLocation); + operationResult = await new StorageLocationDataLayer(client).DeleteAsync(storageLocation); + Assert.True(operationResult.IsSuccessStatusCode, "Failed to delete the storage location."); List? stocks = await dataLayer.GetAllAsync(areaAsset.Integer64ID); @@ -487,31 +453,28 @@ public async Task VerifyGetSingleStockWithId() Asset? areaAsset = await DataHelper.GetOrCreateAreaAssetAsync(client, Constants.TestAreaAsset); - if (areaAsset == null) + if (areaAsset is null) { Assert.Fail("Failed to retrieve or create the area asset."); - return; } StorageLocation? storageLocation = await DataHelper.GetOrCreateStorageLocationAsync(client, Constants.TestStorageLocation, areaAsset.Integer64ID); - if (storageLocation == null) + if (storageLocation is null) { Assert.Fail("Failed to retrieve or create the storage location."); - return; } Part? part = await DataHelper.GetOrCreatePartAsync(client, "Get Single Stock Test"); - if (part == null) + if (part is null) { Assert.Fail("Failed to retrieve or create the part."); - return; } OperationResult operationResult = await dataLayer.CreateAsync(new Stock() { Amount = 0, Name = "Get Single Stock Test", OwnerInteger64ID = part.Integer64ID, StorageLocationID = storageLocation.Integer64ID, StorageLocationName = storageLocation.FriendlyName }); - if (operationResult.DataObject is Stock createdPartStock) + if (operationResult.IsSuccessStatusCode && operationResult.DataObject is Stock createdPartStock) { Stock? fetchedPartStock = await dataLayer.GetSingleAsync(createdPartStock.Integer64ID); Assert.NotNull(fetchedPartStock); @@ -534,26 +497,23 @@ public async Task VerifyRenameStorageLocationCascade() Asset? areaAsset = await DataHelper.GetOrCreateAreaAssetAsync(client, Constants.TestAreaAsset); - if (areaAsset == null) + if (areaAsset is null) { Assert.Fail("Failed to retrieve or create the area asset."); - return; } StorageLocation? storageLocation = await DataHelper.GetOrCreateStorageLocationAsync(client, "Rename Storage Location Test", areaAsset.Integer64ID); - if (storageLocation == null) + if (storageLocation is null) { Assert.Fail("Failed to retrieve or create the storage location."); - return; } Part? part = await DataHelper.GetOrCreatePartAsync(client, Constants.TestPart); - if (part == null) + if (part is null) { Assert.Fail("Failed to retrieve or create the part."); - return; } OperationResult operationResult = await dataLayer.CreateAsync(new Stock() @@ -565,16 +525,11 @@ public async Task VerifyRenameStorageLocationCascade() StorageLocationName = storageLocation.FriendlyName, }); - if (operationResult.DataObject is Stock createdPartStock) + if (operationResult.IsSuccessStatusCode && operationResult.DataObject is Stock createdPartStock) { storageLocation.LocationA = "New Storage Location Test"; operationResult = await new StorageLocationDataLayer(client).UpdateAsync(storageLocation); - - if (!operationResult.IsSuccessStatusCode) - { - Assert.Fail("Failed to update the storage location."); - return; - } + Assert.True(operationResult.IsSuccessStatusCode, "Failed to update the storage location."); Stock? fetchedPartStock = await dataLayer.GetSingleAsync(createdPartStock.Integer64ID); @@ -585,7 +540,6 @@ public async Task VerifyRenameStorageLocationCascade() else { Assert.Fail("Failed to create the stock."); - return; } } @@ -607,26 +561,23 @@ public async Task VerifyUpdateStock(string locationName, string partName, decima Asset? areaAsset = await DataHelper.GetOrCreateAreaAssetAsync(client, Constants.TestAreaAsset); - if (areaAsset == null) + if (areaAsset is null) { Assert.Fail("Failed to retrieve or create the area asset."); - return; } StorageLocation? storageLocation = await DataHelper.GetOrCreateStorageLocationAsync(client, locationName, areaAsset.Integer64ID); - if (storageLocation == null) + if (storageLocation is null) { Assert.Fail("Failed to retrieve or create the storage location."); - return; } Part? part = await DataHelper.GetOrCreatePartAsync(client, partName); - if (part == null) + if (part is null) { Assert.Fail("Failed to retrieve or create the part."); - return; } OperationResult operationResult = await dataLayer.CreateAsync(new Stock() @@ -668,26 +619,23 @@ public async Task VerifyUpdateStockDependenciesNotExists() Asset? areaAsset = await DataHelper.GetOrCreateAreaAssetAsync(client, Constants.TestAreaAsset); - if (areaAsset == null) + if (areaAsset is null) { Assert.Fail("Failed to retrieve or create the area asset."); - return; } StorageLocation? storageLocation = await DataHelper.GetOrCreateStorageLocationAsync(client, Constants.TestStorageLocation, areaAsset.Integer64ID); - if (storageLocation == null) + if (storageLocation is null) { Assert.Fail("Failed to retrieve or create the storage location."); - return; } Part? part = await DataHelper.GetOrCreatePartAsync(client, "Update Dependencies Not Exists Stock Test"); - if (part == null) + if (part is null) { Assert.Fail("Failed to retrieve or create the part."); - return; } OperationResult operationResult = await dataLayer.CreateAsync(new Stock() @@ -699,10 +647,10 @@ public async Task VerifyUpdateStockDependenciesNotExists() StorageLocationName = storageLocation.FriendlyName, }); - if (operationResult.DataObject is Stock stock) + if (operationResult.IsSuccessStatusCode && operationResult.DataObject is Stock stock) { stock.OwnerInteger64ID = 0; - stock.StorageLocationID = 0; + stock.StorageLocationID = BadStorageLocationID; operationResult = await dataLayer.UpdateAsync(stock); //The operation must have failed. @@ -714,20 +662,18 @@ public async Task VerifyUpdateStockDependenciesNotExists() //A bad request status was returned. Assert.Equal(HttpStatusCode.BadRequest, operationResult.StatusCode); - //A validation error was returned. - Assert.NotNull(operationResult.ServerSideValidationResult); - Assert.Equal(2, operationResult.ServerSideValidationResult.Errors.Count); - //The correct error was returned. - Assert.Contains("part was not found", operationResult.ServerSideValidationResult.Errors[0].ErrorMessage); - Assert.Equal(nameof(Stock.OwnerInteger64ID), operationResult.ServerSideValidationResult.Errors[0].PropertyName); - Assert.Contains("storage location was not found", operationResult.ServerSideValidationResult.Errors[1].ErrorMessage); - Assert.Equal(nameof(Stock.StorageLocationID), operationResult.ServerSideValidationResult.Errors[1].PropertyName); + Assert.Contains(operationResult.ValidationErrors, obj => obj.Key == nameof(Stock.OwnerInteger64ID)); + Assert.Single(operationResult.ValidationErrors[nameof(Stock.OwnerInteger64ID)]); + Assert.Contains("part was not found", operationResult.ValidationErrors[nameof(Stock.OwnerInteger64ID)][0]); + + Assert.Contains(operationResult.ValidationErrors, obj => obj.Key == nameof(Stock.StorageLocationID)); + Assert.Single(operationResult.ValidationErrors[nameof(Stock.StorageLocationID)]); + Assert.Contains("storage location was not found", operationResult.ValidationErrors[nameof(Stock.StorageLocationID)][0]); } else { Assert.Fail("Failed to create the stock."); - return; } } @@ -743,26 +689,23 @@ public async Task UpdateStockOldDataAsync() Asset? areaAsset = await DataHelper.GetOrCreateAreaAssetAsync(client, Constants.TestAreaAsset); - if (areaAsset == null) + if (areaAsset is null) { Assert.Fail("Failed to retrieve or create the area asset."); - return; } StorageLocation? storageLocation = await DataHelper.GetOrCreateStorageLocationAsync(client, Constants.TestStorageLocation, areaAsset.Integer64ID); - if (storageLocation == null) + if (storageLocation is null) { Assert.Fail("Failed to retrieve or create the storage location."); - return; } Part? part = await DataHelper.GetOrCreatePartAsync(client, "Stock Old Data Test"); - if (part == null) + if (part is null) { Assert.Fail("Failed to retrieve or create the part."); - return; } OperationResult operationResult = await dataLayer.CreateAsync(new Stock() @@ -774,7 +717,7 @@ public async Task UpdateStockOldDataAsync() StorageLocationName = storageLocation.FriendlyName, }); - if (operationResult.DataObject is Stock firstPartStock) + if (operationResult.IsSuccessStatusCode && operationResult.DataObject is Stock firstPartStock) { Stock secondPartStock = new(firstPartStock); @@ -782,12 +725,7 @@ public async Task UpdateStockOldDataAsync() secondPartStock.Amount = 10; operationResult = await dataLayer.UpdateAsync(secondPartStock); - - if (!operationResult.IsSuccessStatusCode) - { - Assert.Fail("Failed to update the second stock."); - return; - } + Assert.True(operationResult.IsSuccessStatusCode, "Failed to update the second stock."); operationResult = await dataLayer.UpdateAsync(firstPartStock); @@ -798,7 +736,6 @@ public async Task UpdateStockOldDataAsync() else { Assert.Fail("Failed to create the stock."); - return; } } } diff --git a/TestProject/TestProject.csproj b/TestProject/TestProject.csproj index 4728f34..5e356dd 100644 --- a/TestProject/TestProject.csproj +++ b/TestProject/TestProject.csproj @@ -1,7 +1,7 @@ - net8.0 + net9.0 enable enable @@ -11,8 +11,7 @@ - - +