From 0ce604e5f9c93dcd008d52c1961b9d5ea252baaf Mon Sep 17 00:00:00 2001 From: Zyzf Date: Mon, 17 Apr 2023 20:09:21 +0400 Subject: [PATCH 1/4] final --- .../DataListSingleton.cs | 6 +- .../Implements/ImplementerStorage.cs | 114 +++++ .../Models/Implementer.cs | 54 +++ .../BusinessLogics/ImplementerLogic.cs | 121 +++++ .../BusinessLogics/OrderLogic.cs | 113 +++-- .../BusinessLogics/WorkModeling.cs | 143 ++++++ .../Views/Shared/_Layout.cshtml | 2 +- .../BindingModels/ImplementerBindingModel.cs | 18 + .../BindingModels/OrderBindingModel.cs | 3 +- .../IImplementerLogic.cs | 20 + .../BusinessLogicsContracts/IOrderLogic.cs | 3 +- .../BusinessLogicsContracts/IWorkProcess.cs | 16 + .../SearchModels/ImplementerSearchModel.cs | 15 + .../SearchModels/OrderSearchModel.cs | 9 +- .../StorageContracts/IImplementerStorage.cs | 21 + .../ViewModels/ImplementerViewModel.cs | 26 ++ .../ViewModels/OrderViewModel.cs | 5 +- .../Models/IImplementerModel.cs | 16 + .../BlacksmithWorkshopDatabase.cs | 3 +- .../Implements/ImplementerStorage.cs | 83 ++++ .../Implements/OrderStorage.cs | 58 ++- ...20230417131353_WithImplementer.Designer.cs | 257 +++++++++++ .../20230417131353_WithImplementer.cs | 108 +++++ ...BlacksmithWorkshopDatabaseModelSnapshot.cs | 53 ++- .../Models/Implementer.cs | 57 +++ .../Models/Order.cs | 20 +- .../DataFileSingleton.cs | 12 +- .../Implements/ImplementerStorage.cs | 83 ++++ .../Models/Implementer.cs | 76 ++++ .../Models/Order.cs | 13 +- .../Controllers/ImplementerController.cs | 99 ++++ .../BlacksmithWorkshopRestApi/Program.cs | 2 + .../FormImplementer.Designer.cs | 162 +++++++ .../BlacksmithWorkshopView/FormImplementer.cs | 108 +++++ .../FormImplementer.resx | 120 +++++ .../FormImplementers.Designer.cs | 115 +++++ .../FormImplementers.cs | 110 +++++ .../FormImplementers.resx | 120 +++++ .../FormMain.Designer.cs | 430 +++++++++--------- .../BlacksmithWorkshopView/FormMain.cs | 310 ++++++------- .../BlacksmithWorkshopView/Program.cs | 6 +- 41 files changed, 2628 insertions(+), 482 deletions(-) create mode 100644 BlacksmithWorkshop/BlacksmithListImplement/Implements/ImplementerStorage.cs create mode 100644 BlacksmithWorkshop/BlacksmithListImplement/Models/Implementer.cs create mode 100644 BlacksmithWorkshop/BlacksmithWorkshopBusinessLogic/BusinessLogics/ImplementerLogic.cs create mode 100644 BlacksmithWorkshop/BlacksmithWorkshopBusinessLogic/BusinessLogics/WorkModeling.cs create mode 100644 BlacksmithWorkshop/BlacksmithWorkshopContracts/BindingModels/ImplementerBindingModel.cs create mode 100644 BlacksmithWorkshop/BlacksmithWorkshopContracts/BusinessLogicsContracts/IImplementerLogic.cs create mode 100644 BlacksmithWorkshop/BlacksmithWorkshopContracts/BusinessLogicsContracts/IWorkProcess.cs create mode 100644 BlacksmithWorkshop/BlacksmithWorkshopContracts/SearchModels/ImplementerSearchModel.cs create mode 100644 BlacksmithWorkshop/BlacksmithWorkshopContracts/StorageContracts/IImplementerStorage.cs create mode 100644 BlacksmithWorkshop/BlacksmithWorkshopContracts/ViewModels/ImplementerViewModel.cs create mode 100644 BlacksmithWorkshop/BlacksmithWorkshopDataModels/Models/IImplementerModel.cs create mode 100644 BlacksmithWorkshop/BlacksmithWorkshopDatabaseImplement/Implements/ImplementerStorage.cs create mode 100644 BlacksmithWorkshop/BlacksmithWorkshopDatabaseImplement/Migrations/20230417131353_WithImplementer.Designer.cs create mode 100644 BlacksmithWorkshop/BlacksmithWorkshopDatabaseImplement/Migrations/20230417131353_WithImplementer.cs create mode 100644 BlacksmithWorkshop/BlacksmithWorkshopDatabaseImplement/Models/Implementer.cs create mode 100644 BlacksmithWorkshop/BlacksmithWorkshopFileImplement/Implements/ImplementerStorage.cs create mode 100644 BlacksmithWorkshop/BlacksmithWorkshopFileImplement/Models/Implementer.cs create mode 100644 BlacksmithWorkshop/BlacksmithWorkshopRestApi/Controllers/ImplementerController.cs create mode 100644 BlacksmithWorkshop/BlacksmithWorkshopView/FormImplementer.Designer.cs create mode 100644 BlacksmithWorkshop/BlacksmithWorkshopView/FormImplementer.cs create mode 100644 BlacksmithWorkshop/BlacksmithWorkshopView/FormImplementer.resx create mode 100644 BlacksmithWorkshop/BlacksmithWorkshopView/FormImplementers.Designer.cs create mode 100644 BlacksmithWorkshop/BlacksmithWorkshopView/FormImplementers.cs create mode 100644 BlacksmithWorkshop/BlacksmithWorkshopView/FormImplementers.resx diff --git a/BlacksmithWorkshop/BlacksmithListImplement/DataListSingleton.cs b/BlacksmithWorkshop/BlacksmithListImplement/DataListSingleton.cs index 2019e4b..2d81b17 100644 --- a/BlacksmithWorkshop/BlacksmithListImplement/DataListSingleton.cs +++ b/BlacksmithWorkshop/BlacksmithListImplement/DataListSingleton.cs @@ -15,13 +15,15 @@ namespace BlacksmithWorkshopListImplement public List Orders { get; set; } public List Manufactures { get; set; } public List Clients { get; set; } - private DataListSingleton() + public List Implementers { get; set; } + private DataListSingleton() { Components = new List(); Orders = new List(); Manufactures = new List(); Clients = new List(); - } + Implementers = new List(); + } public static DataListSingleton GetInstance() { if (_instance == null) diff --git a/BlacksmithWorkshop/BlacksmithListImplement/Implements/ImplementerStorage.cs b/BlacksmithWorkshop/BlacksmithListImplement/Implements/ImplementerStorage.cs new file mode 100644 index 0000000..282640e --- /dev/null +++ b/BlacksmithWorkshop/BlacksmithListImplement/Implements/ImplementerStorage.cs @@ -0,0 +1,114 @@ +using BlacksmithWorkshopContracts.BindingModels; +using BlacksmithWorkshopContracts.SearchModels; +using BlacksmithWorkshopContracts.StorageContracts; +using BlacksmithWorkshopContracts.ViewModels; +using BlacksmithWorkshopListImplement.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BlacksmithWorkshopListImplement.Implements +{ + public class ImplementerStorage : IImplementerStorage + { + private readonly DataListSingleton _source; + public ImplementerStorage() + { + _source = DataListSingleton.GetInstance(); + } + public ImplementerViewModel? Delete(ImplementerBindingModel model) + { + for (int i = 0; i < _source.Implementers.Count; ++i) + { + if (_source.Implementers[i].Id == model.Id) + { + var element = _source.Implementers[i]; + _source.Implementers.RemoveAt(i); + return element.GetViewModel; + } + } + return null; + } + public ImplementerViewModel? GetElement(ImplementerSearchModel model) + { + if (model.Id.HasValue) + { + foreach (var implementer in _source.Implementers) + { + if (implementer.Id == model.Id) + { + return implementer.GetViewModel; + } + } + } + else if (!string.IsNullOrEmpty(model.ImplementerFIO)) + { + foreach (var implementer in _source.Implementers) + { + if (implementer.ImplementerFIO == model.ImplementerFIO) + { + return implementer.GetViewModel; + } + } + } + return null; + } + public List GetFilteredList(ImplementerSearchModel model) + { + var result = new List(); + if (string.IsNullOrEmpty(model.ImplementerFIO)) + { + return result; + } + foreach (var implementer in _source.Implementers) + { + if (implementer.ImplementerFIO.Contains(model.ImplementerFIO)) + { + result.Add(implementer.GetViewModel); + } + } + return result; + } + public List GetFullList() + { + var result = new List(); + foreach (var implementer in _source.Implementers) + { + result.Add(implementer.GetViewModel); + } + return result; + } + public ImplementerViewModel? Insert(ImplementerBindingModel model) + { + model.Id = 1; + foreach (var implementer in _source.Implementers) + { + if (model.Id <= implementer.Id) + { + model.Id = implementer.Id + 1; + } + } + var newImplementer = Implementer.Create(model); + if (newImplementer == null) + { + return null; + } + _source.Implementers.Add(newImplementer); + return newImplementer.GetViewModel; + } + public ImplementerViewModel? Update(ImplementerBindingModel model) + { + foreach (var implementer in _source.Implementers) + { + if (implementer.Id == model.Id) + { + implementer.Update(model); + return implementer.GetViewModel; + } + } + return null; + } + } +} diff --git a/BlacksmithWorkshop/BlacksmithListImplement/Models/Implementer.cs b/BlacksmithWorkshop/BlacksmithListImplement/Models/Implementer.cs new file mode 100644 index 0000000..0e7d98e --- /dev/null +++ b/BlacksmithWorkshop/BlacksmithListImplement/Models/Implementer.cs @@ -0,0 +1,54 @@ +using BlacksmithWorkshopContracts.BindingModels; +using BlacksmithWorkshopContracts.ViewModels; +using BlacksmithWorkshopDataModels.Models; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BlacksmithWorkshopListImplement.Models +{ + public class Implementer : IImplementerModel + { + public int Id { get; private set; } + public string ImplementerFIO { get; private set; } = string.Empty; + public string Password { get; private set; } = string.Empty; + public int WorkExperience { get; private set; } + public int Qualification { get; private set; } + public static Implementer? Create(ImplementerBindingModel? model) + { + if (model == null) + { + return null; + } + return new Implementer() + { + Id = model.Id, + ImplementerFIO = model.ImplementerFIO, + Password = model.Password, + WorkExperience = model.WorkExperience, + Qualification = model.Qualification, + }; + } + public void Update(ImplementerBindingModel? model) + { + if (model == null) + { + return; + } + ImplementerFIO = model.ImplementerFIO; + Password = model.Password; + WorkExperience = model.WorkExperience; + Qualification = model.Qualification; + } + public ImplementerViewModel GetViewModel => new() + { + Id = Id, + ImplementerFIO = ImplementerFIO, + Password = Password, + WorkExperience = WorkExperience, + Qualification = Qualification + }; + } +} diff --git a/BlacksmithWorkshop/BlacksmithWorkshopBusinessLogic/BusinessLogics/ImplementerLogic.cs b/BlacksmithWorkshop/BlacksmithWorkshopBusinessLogic/BusinessLogics/ImplementerLogic.cs new file mode 100644 index 0000000..656183e --- /dev/null +++ b/BlacksmithWorkshop/BlacksmithWorkshopBusinessLogic/BusinessLogics/ImplementerLogic.cs @@ -0,0 +1,121 @@ +using BlacksmithWorkshopContracts.BindingModels; +using BlacksmithWorkshopContracts.BusinessLogicsContracts; +using BlacksmithWorkshopContracts.SearchModels; +using BlacksmithWorkshopContracts.StorageContracts; +using BlacksmithWorkshopContracts.ViewModels; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BlacksmithWorkshopBusinessLogic.BusinessLogics +{ + public class ImplementerLogic : IImplementerLogic + { + private readonly ILogger _logger; + private readonly IImplementerStorage _implementerStorage; + public ImplementerLogic(ILogger logger, IImplementerStorage implementerStorage) + { + _logger = logger; + _implementerStorage = implementerStorage; + } + public ImplementerViewModel? ReadElement(ImplementerSearchModel model) + { + if (model == null) + { + throw new ArgumentNullException(nameof(model)); + } + _logger.LogInformation("ReadElement. ImplementerFIO:{ImplementerFIO}. Id:{Id}", model.ImplementerFIO, model.Id); + var element = _implementerStorage.GetElement(model); + if (element == null) + { + _logger.LogWarning("ReadElement element not found"); + return null; + } + _logger.LogInformation("ReadElement find. Id:{Id}", element.Id); + return element; + } + public List? ReadList(ImplementerSearchModel? model) + { + _logger.LogInformation("ReadList. ImplementerFIO:{ImplementerFIO}. Id:{Id}", model?.ImplementerFIO, model?.Id); + var list = model == null ? _implementerStorage.GetFullList() : _implementerStorage.GetFilteredList(model); + if (list == null) + { + _logger.LogWarning("ReadList return null list"); + return null; + } + _logger.LogInformation("ReadList. Count:{Count}", list.Count); + return list; + } + public bool Create(ImplementerBindingModel model) + { + CheckModel(model); + if (_implementerStorage.Insert(model) == null) + { + _logger.LogWarning("Insert operation failed"); + return false; + } + return true; + } + public bool Update(ImplementerBindingModel model) + { + CheckModel(model); + if (_implementerStorage.Update(model) == null) + { + _logger.LogWarning("Update operation failed"); + return false; + } + return true; + } + public bool Delete(ImplementerBindingModel model) + { + CheckModel(model, false); + _logger.LogInformation("Delete. Id:{Id}", model.Id); + if (_implementerStorage.Delete(model) == null) + { + _logger.LogWarning("Delete operation failed"); + return false; + } + return true; + } + private void CheckModel(ImplementerBindingModel model, bool withParams = true) + { + if (model == null) + { + throw new ArgumentNullException(nameof(model)); + } + if (!withParams) + { + return; + } + if (string.IsNullOrEmpty(model.ImplementerFIO)) + { + throw new ArgumentNullException("Нет ФИО", nameof(model.ImplementerFIO)); + } + if (model.Qualification <= 0) + { + throw new ArgumentNullException("Квалификация должна быть больше 0", nameof(model.Qualification)); + } + if (model.WorkExperience <= 0) + { + throw new ArgumentNullException("Стаж должен быть больше 0", nameof(model.WorkExperience)); + } + if (string.IsNullOrEmpty(model.Password)) + { + throw new ArgumentNullException("Нет пароля", nameof(model.Password)); + } + _logger.LogInformation("Implementer. ImplementerFIO:{ImplementerFIO}. Password:{Password}. Qualification:{Qualification}. WorkExperience:{WorkExperience}. Id:{Id}", + model.ImplementerFIO, model.Password, model.Qualification, model.WorkExperience, model.Id); + var element = _implementerStorage.GetElement(new ImplementerSearchModel + { + ImplementerFIO = model.ImplementerFIO + }); + if (element != null && element.Id != model.Id) + { + throw new InvalidOperationException("Исполнитель с таким ФИО уже есть"); + } + } + } +} diff --git a/BlacksmithWorkshop/BlacksmithWorkshopBusinessLogic/BusinessLogics/OrderLogic.cs b/BlacksmithWorkshop/BlacksmithWorkshopBusinessLogic/BusinessLogics/OrderLogic.cs index 9187cfc..7c93820 100644 --- a/BlacksmithWorkshop/BlacksmithWorkshopBusinessLogic/BusinessLogics/OrderLogic.cs +++ b/BlacksmithWorkshop/BlacksmithWorkshopBusinessLogic/BusinessLogics/OrderLogic.cs @@ -35,35 +35,51 @@ namespace BlacksmithWorkshopBusinessLogic.BusinessLogics _logger.LogInformation("ReadList. Count:{Count}", list.Count); return list; } - public bool CreateOrder(OrderBindingModel model) + public OrderViewModel? ReadElement(OrderSearchModel model) + { + if (model == null) + { + throw new ArgumentNullException(nameof(model)); + } + _logger.LogInformation("ReadElement. DateFrom:{DateFrom}. DateTo:{DateTo}. Id:{Id}", model.DateFrom, model.DateTo, model.Id); + var element = _orderStorage.GetElement(model); + if (element == null) + { + _logger.LogWarning("ReadElement element not found"); + return null; + } + _logger.LogInformation("ReadElement find. Id:{Id}", element.Id); + return element; + } + public bool CreateOrder(OrderBindingModel model) { - CheckModel(model); - if (!CheckStatus(model, OrderStatus.Принят, false)) return false; - if (_orderStorage.Insert(model) == null) - { - _logger.LogWarning("Insert operation failed"); - return false; - } - return true; - } + CheckModel(model); + if (model.Status != OrderStatus.Неизвестен) + { + _logger.LogWarning("Insert operation failed. Order status is incorrect."); + return false; + } + model.Status = OrderStatus.Принят; + if (_orderStorage.Insert(model) == null) + { + model.Status = OrderStatus.Неизвестен; + _logger.LogWarning("Insert operation failed"); + return false; + } + return true; + } public bool TakeOrderInWork(OrderBindingModel model) { - CheckModel(model); - if (!CheckStatus(model, OrderStatus.Выполняется)) return false; - return true; - } + return StatusUpdate(model, OrderStatus.Выполняется); + } public bool DeliveryOrder(OrderBindingModel model) { - CheckModel(model); - if (!CheckStatus(model, OrderStatus.Выдан)) return false; - return true; - } + return StatusUpdate(model, OrderStatus.Выдан); + } public bool FinishOrder(OrderBindingModel model) { - CheckModel(model); - if (!CheckStatus(model, OrderStatus.Готов)) return false; - return true; - } + return StatusUpdate(model, OrderStatus.Готов); + } private void CheckModel(OrderBindingModel model, bool withParams = true) { if (model == null) @@ -88,23 +104,38 @@ namespace BlacksmithWorkshopBusinessLogic.BusinessLogics } _logger.LogInformation("Order. OrderId: {Id}.Sum: {Sum}. ManufactureId: {ManufactureId}", model.Id, model.Sum, model.ManufactureId); } - - private bool CheckStatus(OrderBindingModel model, OrderStatus newstatus, bool update = true) - { - if (model.Status != newstatus - 1) - { - _logger.LogWarning("Failed to change status"); - return false; - } - model.Status = newstatus; - if (!update) return true; - if (_orderStorage.Update(model) == null) - { - _logger.LogWarning("Insert operation failed"); - return false; - } - if (model.Status == OrderStatus.Выдан) model.DateImplement = DateTime.Now; - return true; - } - } + private bool StatusUpdate(OrderBindingModel model, OrderStatus newStatus) + { + var viewModel = _orderStorage.GetElement(new OrderSearchModel { Id = model.Id }); + if (viewModel == null) + { + throw new ArgumentNullException(nameof(model)); + } + if (viewModel.Status + 1 != newStatus) + { + _logger.LogWarning("Change status operation failed"); + throw new InvalidOperationException(); + } + model.Status = newStatus; + if (model.Status == OrderStatus.Готов) + { + model.DateImplement = DateTime.Now; + } + else + { + model.DateImplement = viewModel.DateImplement; + } + if (viewModel.ImplementerId.HasValue) + { + model.ImplementerId = viewModel.ImplementerId.Value; + } + CheckModel(model, false); + if (_orderStorage.Update(model) == null) + { + _logger.LogWarning("Change status operation failed"); + return false; + } + return true; + } + } } diff --git a/BlacksmithWorkshop/BlacksmithWorkshopBusinessLogic/BusinessLogics/WorkModeling.cs b/BlacksmithWorkshop/BlacksmithWorkshopBusinessLogic/BusinessLogics/WorkModeling.cs new file mode 100644 index 0000000..44d0793 --- /dev/null +++ b/BlacksmithWorkshop/BlacksmithWorkshopBusinessLogic/BusinessLogics/WorkModeling.cs @@ -0,0 +1,143 @@ +using BlacksmithWorkshopContracts.BindingModels; +using BlacksmithWorkshopContracts.BusinessLogicsContracts; +using BlacksmithWorkshopContracts.SearchModels; +using BlacksmithWorkshopContracts.ViewModels; +using BlacksmithWorkshopDataModels.Enums; +using Microsoft.Extensions.Logging; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace BlacksmithWorkshopBusinessLogic.BusinessLogics +{ + public class WorkModeling : IWorkProcess + { + private readonly ILogger _logger; + private readonly Random _rnd; + private IOrderLogic? _orderLogic; + public WorkModeling(ILogger logger) + { + _logger = logger; + _rnd = new Random(1000); + } + public void DoWork(IImplementerLogic implementerLogic, IOrderLogic orderLogic) + { + _orderLogic = orderLogic; + var implementers = implementerLogic.ReadList(null); + if (implementers == null) + { + _logger.LogWarning("DoWork. Implementers is null"); + return; + } + var orders = _orderLogic.ReadList(new OrderSearchModel { Status = OrderStatus.Принят }); + if (orders == null || orders.Count == 0) + { + _logger.LogWarning("DoWork. Orders is null or empty"); + return; + } + _logger.LogDebug("DoWork for {Count} orders", orders.Count); + foreach (var implementer in implementers) + { + Task.Run(() => WorkerWorkAsync(implementer, orders)); + } + } + /// + /// Иммитация работы исполнителя + /// + /// + /// + private async Task WorkerWorkAsync(ImplementerViewModel implementer, List orders) + { + if (_orderLogic == null || implementer == null) + { + return; + } + await RunOrderInWork(implementer); + + await Task.Run(() => + { + foreach (var order in orders) + { + try + { + _logger.LogDebug("DoWork. Worker {Id} try get order {Order}", implementer.Id, order.Id); + // пытаемся назначить заказ на исполнителя + _orderLogic.TakeOrderInWork(new OrderBindingModel + { + Id = order.Id, + ImplementerId = implementer.Id + }); + // делаем работу + Thread.Sleep(implementer.WorkExperience * _rnd.Next(100, 1000) * order.Count); + _logger.LogDebug("DoWork. Worker {Id} finish order {Order}", implementer.Id, order.Id); + _orderLogic.FinishOrder(new OrderBindingModel + { + Id = order.Id + }); + // отдыхаем + Thread.Sleep(implementer.Qualification * _rnd.Next(10, 100)); + } + // кто-то мог уже перехватить заказ, игнорируем ошибку + catch (InvalidOperationException ex) + { + _logger.LogWarning(ex, "Error try get work"); + } + // заканчиваем выполнение имитации в случае иной ошибки + catch (Exception ex) + { + _logger.LogError(ex, "Error while do work"); + throw; + } + } + }); + } + /// + /// Ищем заказ, которые уже в работе (вдруг исполнителя прервали) + /// + /// + /// + private async Task RunOrderInWork(ImplementerViewModel implementer) + { + if (_orderLogic == null || implementer == null) + { + return; + } + try + { + var runOrder = await Task.Run(() => _orderLogic.ReadElement(new OrderSearchModel + { + ImplementerId = implementer.Id, + Status = OrderStatus.Выполняется + })); + if (runOrder == null) + { + return; + } + + _logger.LogDebug("DoWork. Worker {Id} back to order {Order}", implementer.Id, runOrder.Id); + // доделываем работу + Thread.Sleep(implementer.WorkExperience * _rnd.Next(100, 300) * runOrder.Count); + _logger.LogDebug("DoWork. Worker {Id} finish order {Order}", implementer.Id, runOrder.Id); + _orderLogic.FinishOrder(new OrderBindingModel + { + Id = runOrder.Id + }); + // отдыхаем + Thread.Sleep(implementer.Qualification * _rnd.Next(10, 100)); + } + // заказа может не быть, просто игнорируем ошибку + catch (InvalidOperationException ex) + { + _logger.LogWarning(ex, "Error try get work"); + } + // а может возникнуть иная ошибка, тогда просто заканчиваем выполнение имитации + catch (Exception ex) + { + _logger.LogError(ex, "Error while do work"); + throw; + } + } + } +} diff --git a/BlacksmithWorkshop/BlacksmithWorkshopClientApp/Views/Shared/_Layout.cshtml b/BlacksmithWorkshop/BlacksmithWorkshopClientApp/Views/Shared/_Layout.cshtml index 8cf8e71..117c713 100644 --- a/BlacksmithWorkshop/BlacksmithWorkshopClientApp/Views/Shared/_Layout.cshtml +++ b/BlacksmithWorkshop/BlacksmithWorkshopClientApp/Views/Shared/_Layout.cshtml @@ -9,6 +9,7 @@ + @await RenderSectionAsync("Scripts", required: false)