├── BookApp ├── Views │ ├── _ViewStart.cshtml │ ├── _ViewImports.cshtml │ ├── Admin │ │ ├── BookUpdated.cshtml │ │ ├── ChangePubDate.cshtml │ │ ├── AddBookReview.cshtml │ │ └── ChangePromotion.cshtml │ ├── Shared │ │ ├── _ValidationScriptsPartial.cshtml │ │ ├── Error.cshtml │ │ └── LogModal.cshtml │ ├── Checkout │ │ ├── Index.cshtml │ │ └── PlaceOrder.cshtml │ ├── Home │ │ ├── Privacy.cshtml │ │ └── About.cshtml │ └── Orders │ │ ├── Index.cshtml │ │ ├── ConfirmOrder.cshtml │ │ └── OneOrderPartial.cshtml ├── Startup.cs ├── wwwroot │ ├── favicon.ico │ ├── lib │ │ ├── jquery-validation-unobtrusive │ │ │ └── LICENSE.txt │ │ ├── jquery-validation │ │ │ └── LICENSE.md │ │ ├── bootstrap │ │ │ └── LICENSE │ │ └── jquery │ │ │ └── LICENSE.txt │ └── js │ │ └── bundle.min.js ├── bundleconfig.json ├── appsettings.json ├── appsettings.Development.json ├── Models │ └── ErrorViewModel.cs ├── Controllers │ ├── LoggerController.cs │ ├── BaseTraceController.cs │ └── OrdersController.cs ├── Properties │ └── launchSettings.json ├── Program.cs ├── HelperExtensions │ ├── IsLocalExtension.cs │ └── DatabaseStartupHelpers.cs ├── BookApp.csproj └── Logger │ └── RequestTransientLogger.cs ├── Test ├── appsettings.json ├── Chapter06Listings │ ├── Many1.cs │ ├── Many2.cs │ ├── Many3.cs │ ├── OnePrincipal.cs │ ├── ReviewNotSafe.cs │ ├── OneDependent.cs │ ├── ManyTop.cs │ ├── BookNotSafe.cs │ ├── EfManyExtension.cs │ ├── Chapter06Context.cs │ ├── Employee.cs │ └── EmployeeExtensions.cs ├── Chapter03Listings │ ├── EfCode │ │ ├── SimpleCreateSql.sql │ │ └── Chapter3DbContext.cs │ └── EfClasses │ │ ├── ExampleEntity.cs │ │ ├── TagCheckSet.cs │ │ ├── ReviewSetCheck.cs │ │ ├── Author.cs │ │ ├── BookCheckSet.cs │ │ └── BookAuthorCheckSet.cs ├── Chapter01Listings │ ├── Person.cs │ └── Chapter01DbContext.cs ├── Chapter02Listings │ ├── Lazy1Review.cs │ ├── Lazy2Review.cs │ ├── LazyReview.cs │ ├── LazyProxyContext.cs │ ├── LazyInjectContext.cs │ ├── BookLazyProxy.cs │ ├── BookHashContext.cs │ ├── BookHashReview.cs │ ├── BookLazy2.cs │ └── BookLazy1.cs ├── Mocks │ ├── TransactBizActionDto.cs │ ├── FakeUserIdService.cs │ ├── MockBizAction.cs │ ├── MockHttpCookieAccess.cs │ ├── MockBizActionAsync.cs │ ├── MockBizActionPart1.cs │ ├── MockBizActionPart2.cs │ ├── FakeResponseCookies.cs │ ├── FakeRequestCookieCollection.cs │ ├── MockBizActionWithWrite.cs │ ├── MockBizActionWithWriteAsync.cs │ └── StubPlaceOrderDbAccess.cs ├── UnitCommands │ └── DeleteAllUnitTestDatabases.cs ├── UnitTests │ ├── TestAspNetCore │ │ ├── TestCalculateReviewsToMatch.cs │ │ ├── TestDatabaseSetupHelpers.cs │ │ └── TestBookJsonLoader.cs │ ├── TestServiceLayer │ │ ├── Ch03_CalculateReviewsToMatch.cs │ │ ├── Ch04_RunnerWriteDb.cs │ │ ├── Ch02_BookJsonLoader.cs │ │ ├── Ch04_RunnersAsync.cs │ │ ├── Ch02_Sort.cs │ │ ├── Ch04_RunnerWriteDbWithValidation.cs │ │ ├── Ch04_RunnerWriteDbWithValidationAsync.cs │ │ ├── Ch03_ChangePriceOfferService.cs │ │ ├── Ch02_ListBooksService.cs │ │ └── Ch02_ListSortFilterPageDto.cs │ ├── TestDataLayer │ │ ├── Ch03_SimpleUpdateSql.sql │ │ ├── Ch06_ComplexQueryOperators.cs │ │ └── Ch03_SpliteInMemory.cs │ ├── TestSupportCode │ │ ├── LinqHelpers.cs │ │ ├── Ch04_MockHttpCookieAccess.cs │ │ ├── Ch04_MockPlaceOrderDbAccess.cs │ │ └── Ch02_AppSettings.cs │ └── TestBizDbAccess │ │ └── Ch04_PlaceOrderDbAccess.cs ├── Chapter05Listings │ ├── ExampleSeed.cs │ ├── ExampleProgram.cs │ └── ExampleMigrateDatabase.cs └── Test.csproj ├── DataLayer ├── DataLayer.csproj ├── EfCode │ ├── IUserIdService.cs │ ├── ReplacementUserIdService.cs │ └── ValidationDbContextServiceProvider.cs ├── EfClasses │ ├── Tag.cs │ ├── Author.cs │ ├── Review.cs │ ├── PriceOffer.cs │ ├── Order.cs │ ├── BookAuthor.cs │ └── Book.cs ├── QueryObjects │ └── GenericPaging.cs └── Migrations │ └── 20200921133547_AddTags.cs ├── MyFirstEfCoreApp ├── MyFirstEfCoreApp.csproj ├── Author.cs ├── Book.cs ├── AppDbContext.cs ├── MyLoggerProvider.cs └── Program.cs ├── BizLogic ├── Orders │ ├── IPlaceOrderAction.cs │ ├── IPlaceOrderPart2.cs │ ├── OrderLineItem.cs │ ├── IPlaceOrderPart1.cs │ ├── Part1ToPart2Dto.cs │ ├── PlaceOrderInDto.cs │ └── Concrete │ │ └── PlaceOrderPart1.cs ├── BizLogic.csproj ├── GenericInterfaces │ ├── IBizActionAsync.cs │ ├── IBizAction.cs │ └── BizActionErrors.cs └── AppStart │ └── NetCoreDiSetupExtensions.cs ├── ServiceLayer ├── AdminServices │ ├── IChangePubDateService.cs │ ├── IAddReviewService.cs │ ├── IChangePriceOfferService.cs │ └── ChangePubDateDto.cs ├── Logger │ ├── TraceIndentGeneric.cs │ ├── TraceIdentBaseDto.cs │ └── LogParts.cs ├── BookServices │ ├── DropdownTuple.cs │ ├── BookListCombinedDto.cs │ ├── BookListDto.cs │ ├── Concrete │ │ └── ListBooksService.cs │ └── QueryObjects │ │ ├── BookListDtoSort.cs │ │ └── BookListDtoSelect.cs ├── OrderServices │ ├── OrderListDto.cs │ └── Concrete │ │ ├── PlaceOrderServiceWithVal.cs │ │ └── DisplayOrdersService.cs ├── CheckoutServices │ ├── CheckoutItemDto.cs │ └── Concrete │ │ ├── BasketCookie.cs │ │ └── CheckoutListService.cs ├── ServiceLayer.csproj ├── DatabaseServices │ ├── Concrete │ │ ├── BookInfoJson.cs │ │ └── SpecialBook.cs │ └── SetupHelpers.cs ├── BizRunners │ ├── RunnerWriteDbAsync.cs │ ├── RunnerWriteDbWithValidationAsync.cs │ ├── RunnerWriteDb.cs │ └── RunnerWriteDbWithValidation.cs ├── AppStart │ └── NetCoreDiSetupExtensions.cs └── DataKeyServices │ └── Concrete │ └── UserIdService.cs ├── BizDbAccess ├── BizDbAccess.csproj └── AppStart │ └── NetCoreDiSetupExtensions.cs ├── LICENSE ├── .vscode ├── tasks.json └── launch.json └── .gitattributes /BookApp/Views/_ViewStart.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | Layout = "_Layout"; 3 | } 4 | -------------------------------------------------------------------------------- /BookApp/Startup.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JonPSmith/EfCoreinAction-SecondEdition/HEAD/BookApp/Startup.cs -------------------------------------------------------------------------------- /BookApp/wwwroot/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JonPSmith/EfCoreinAction-SecondEdition/HEAD/BookApp/wwwroot/favicon.ico -------------------------------------------------------------------------------- /BookApp/Views/_ViewImports.cshtml: -------------------------------------------------------------------------------- 1 | @using BookApp 2 | @using BookApp.Models 3 | @addTagHelper *, Microsoft.AspNetCore.Mvc.TagHelpers 4 | -------------------------------------------------------------------------------- /Test/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionStrings": { 3 | "UnitTestConnection": "Server=(localdb)\\mssqllocaldb;Database=EfCoreInActionDb2-Test;Trusted_Connection=True;MultipleActiveResultSets=true" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /BookApp/Views/Admin/BookUpdated.cshtml: -------------------------------------------------------------------------------- 1 | @model string 2 | 3 |

@Model

4 | 5 |

You can see the SQL that EF Core produced to implement your change.

6 | 7 | @Html.ActionLink("All Books", "Index", "Home") 8 | 9 | -------------------------------------------------------------------------------- /BookApp/bundleconfig.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "outputFileName": "wwwroot/js/bundle.js", 4 | "inputFiles": [ 5 | "wwwroot/js/bookList.js", 6 | "wwwroot/js/loggingDisplay.js" 7 | ] 8 | } 9 | ] 10 | -------------------------------------------------------------------------------- /BookApp/Views/Shared/_ValidationScriptsPartial.cshtml: -------------------------------------------------------------------------------- 1 | 2 | 3 | -------------------------------------------------------------------------------- /BookApp/Views/Checkout/Index.cshtml: -------------------------------------------------------------------------------- 1 | @model System.Collections.Immutable.ImmutableList 2 | @{ 3 | ViewData["Title"] = "Checkout"; 4 | } 5 | 6 | -------------------------------------------------------------------------------- /BookApp/Views/Checkout/PlaceOrder.cshtml: -------------------------------------------------------------------------------- 1 | @model System.Collections.Immutable.ImmutableList 2 | @{ 3 | ViewData["Title"] = "Checkout"; 4 | } 5 | 6 | -------------------------------------------------------------------------------- /BookApp/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "AllowedHosts": "*", 3 | 4 | "Logging": { 5 | "LogLevel": { 6 | "Default": "Information", 7 | "Microsoft": "Warning", 8 | "Microsoft.Hosting.Lifetime": "Information" 9 | } 10 | } 11 | 12 | } 13 | -------------------------------------------------------------------------------- /DataLayer/DataLayer.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /BookApp/Views/Home/Privacy.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "Privacy Policy"; 3 | } 4 |

@ViewData["Title"]

5 | 6 |

7 | The Book App uses a cookie to hold your basket of books you want to buy. 8 |
9 | This cookie also creates a random GUID which becomes your pseudo-UserId. 10 |

11 | -------------------------------------------------------------------------------- /MyFirstEfCoreApp/MyFirstEfCoreApp.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /DataLayer/EfCode/IUserIdService.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System; 5 | 6 | namespace DataLayer.EfCode 7 | { 8 | public interface IUserIdService 9 | { 10 | Guid GetUserId(); 11 | } 12 | } -------------------------------------------------------------------------------- /BookApp/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionStrings": { 3 | "DefaultConnection": "Server=(localdb)\\mssqllocaldb;Database=EfCoreInActionDb2-Part1;Trusted_Connection=True;MultipleActiveResultSets=true" 4 | }, 5 | "Logging": { 6 | "LogLevel": { 7 | "Default": "Debug", 8 | "System": "Information", 9 | "Microsoft": "Information" 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Test/Chapter06Listings/Many1.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | namespace Test.Chapter06Listings 5 | { 6 | public class Many1 7 | { 8 | public int Id { get; set; } 9 | 10 | public int ManyTopId { get; set; } 11 | } 12 | } -------------------------------------------------------------------------------- /Test/Chapter06Listings/Many2.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | namespace Test.Chapter06Listings 5 | { 6 | public class Many2 7 | { 8 | public int Id { get; set; } 9 | 10 | public int ManyTopId { get; set; } 11 | } 12 | } -------------------------------------------------------------------------------- /Test/Chapter06Listings/Many3.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | namespace Test.Chapter06Listings 5 | { 6 | public class Many3 7 | { 8 | public int Id { get; set; } 9 | 10 | public int ManyTopId { get; set; } 11 | } 12 | } -------------------------------------------------------------------------------- /BizLogic/Orders/IPlaceOrderAction.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using BizLogic.GenericInterfaces; 5 | using DataLayer.EfClasses; 6 | 7 | namespace BizLogic.Orders 8 | { 9 | public interface IPlaceOrderAction : IBizAction {} 10 | } -------------------------------------------------------------------------------- /BizLogic/Orders/IPlaceOrderPart2.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using BizLogic.GenericInterfaces; 5 | using DataLayer.EfClasses; 6 | 7 | namespace BizLogic.Orders 8 | { 9 | public interface IPlaceOrderPart2 : IBizAction {} 10 | } -------------------------------------------------------------------------------- /BookApp/Views/Orders/Index.cshtml: -------------------------------------------------------------------------------- 1 | @model List 2 | @{ 3 | ViewData["Title"] = "Orders"; 4 | } 5 | 6 | @if (!Model.Any()) 7 | { 8 |

You haven't bought any books yet.

9 | } 10 | 11 |
12 | 13 | @foreach (var order in Model) 14 | { 15 | 16 |
17 | } 18 | 19 |
20 | -------------------------------------------------------------------------------- /BizLogic/Orders/OrderLineItem.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | namespace BizLogic.Orders 5 | { 6 | public class OrderLineItem 7 | { 8 | public int BookId { get; set; } 9 | 10 | public short NumBooks { get; set; } 11 | } 12 | 13 | 14 | } -------------------------------------------------------------------------------- /Test/Chapter06Listings/OnePrincipal.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | namespace Test.Chapter06Listings 5 | { 6 | public class OnePrincipal 7 | { 8 | public int Id { get; set; } 9 | 10 | public OneDependent Link { get; set; } 11 | } 12 | } -------------------------------------------------------------------------------- /Test/Chapter06Listings/ReviewNotSafe.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | namespace Test.Chapter06Listings 5 | { 6 | public class ReviewNotSafe 7 | { 8 | public int Id { get; set; } 9 | 10 | public int BookNotSafeId { get; set; } 11 | } 12 | } -------------------------------------------------------------------------------- /BizLogic/Orders/IPlaceOrderPart1.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using BizLogic.GenericInterfaces; 5 | using DataLayer.EfClasses; 6 | 7 | namespace BizLogic.Orders 8 | { 9 | public interface IPlaceOrderPart1 : IBizAction {} 10 | } -------------------------------------------------------------------------------- /Test/Chapter03Listings/EfCode/SimpleCreateSql.sql: -------------------------------------------------------------------------------- 1 | SET NOCOUNT ON; 2 | INSERT INTO ExampleEntities] //#A 3 | ([MyMessage]) VALUES (@p0);//#A 4 | 5 | SELECT [ExampleEntityId] //#B 6 | FROM [ExampleEntities] //#B 7 | WHERE @@ROWCOUNT = 1 AND //#B 8 | [ExampleEntityId] = scope_identity(); //#B 9 | #A This inserts (creates) a new row in the table ExampleEntities 10 | #B This reads back the row which was just created 11 | 12 | -------------------------------------------------------------------------------- /Test/Chapter03Listings/EfClasses/ExampleEntity.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | namespace Test.Chapter03Listings.EfClasses 5 | { 6 | public class ExampleEntity 7 | { 8 | public int ExampleEntityId { get; set; } 9 | 10 | public string MyMessage { get; set; } 11 | } 12 | } -------------------------------------------------------------------------------- /BookApp/Models/ErrorViewModel.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System; 5 | 6 | namespace BookApp.Models 7 | { 8 | public class ErrorViewModel 9 | { 10 | public string RequestId { get; set; } 11 | 12 | public bool ShowRequestId => !string.IsNullOrEmpty(RequestId); 13 | } 14 | } -------------------------------------------------------------------------------- /DataLayer/EfCode/ReplacementUserIdService.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System; 5 | 6 | namespace DataLayer.EfCode 7 | { 8 | public class ReplacementUserIdService : IUserIdService 9 | { 10 | public Guid GetUserId() 11 | { 12 | return Guid.Empty; 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /Test/Chapter06Listings/OneDependent.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | namespace Test.Chapter06Listings 5 | { 6 | public class OneDependent 7 | { 8 | public int Id { get; set; } 9 | 10 | public int OnePrincipalId { get; set; } 11 | 12 | public OnePrincipal Link { get; set; } 13 | } 14 | } -------------------------------------------------------------------------------- /BizLogic/BizLogic.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /ServiceLayer/AdminServices/IChangePubDateService.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using DataLayer.EfClasses; 5 | 6 | namespace ServiceLayer.AdminServices 7 | { 8 | public interface IChangePubDateService 9 | { 10 | ChangePubDateDto GetOriginal(int id); 11 | Book UpdateBook(ChangePubDateDto dto); 12 | } 13 | } -------------------------------------------------------------------------------- /BookApp/Views/Orders/ConfirmOrder.cshtml: -------------------------------------------------------------------------------- 1 | @model ServiceLayer.OrderServices.OrderListDto 2 | @{ 3 | ViewData["Title"] = "Confirm Order"; 4 | } 5 | 6 |
7 |

Your order is confirmed

8 | 9 | 10 |

NOTE: This is a demo, so your order will NOT be delivered

11 | 12 | @Html.ActionLink("See all your orders", "Index", new { }, new { @class = "btn btn-primary" }) 13 |
-------------------------------------------------------------------------------- /BizDbAccess/BizDbAccess.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /Test/Chapter01Listings/Person.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | namespace Test.Chapter01Listings 5 | { 6 | public class Person 7 | { 8 | public int Id { get; set; } 9 | public string FirstName { get; set; } 10 | public string LastName { get; set; } 11 | public string FullName => $"{FirstName} {LastName}"; 12 | } 13 | } -------------------------------------------------------------------------------- /ServiceLayer/AdminServices/IAddReviewService.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using DataLayer.EfClasses; 5 | 6 | namespace ServiceLayer.AdminServices 7 | { 8 | public interface IAddReviewService 9 | { 10 | string BookTitle { get; } 11 | 12 | Review GetBlankReview(int id) //#A 13 | ; 14 | 15 | Book AddReviewToBook(Review review)//#D 16 | ; 17 | } 18 | } -------------------------------------------------------------------------------- /DataLayer/EfClasses/Tag.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System.Collections.Generic; 5 | using System.ComponentModel.DataAnnotations; 6 | 7 | namespace DataLayer.EfClasses 8 | { 9 | public class Tag 10 | { 11 | [Key] 12 | [Required] 13 | [MaxLength(40)] 14 | public string TagId { get; set; } 15 | 16 | public ICollection Books { get; set; } 17 | } 18 | } -------------------------------------------------------------------------------- /Test/Chapter01Listings/Chapter01DbContext.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using Microsoft.EntityFrameworkCore; 5 | 6 | namespace Test.Chapter01Listings 7 | { 8 | public class Chapter01DbContext : DbContext 9 | { 10 | public Chapter01DbContext(DbContextOptions options) 11 | : base(options) { } 12 | 13 | public DbSet Persons { get; set; } 14 | } 15 | } -------------------------------------------------------------------------------- /Test/Chapter06Listings/ManyTop.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System.Collections.Generic; 5 | 6 | namespace Test.Chapter06Listings 7 | { 8 | public class ManyTop 9 | { 10 | public int Id { get; set; } 11 | 12 | public IList Collection1 { get; set; } 13 | public IList Collection2 { get; set; } 14 | public IList Collection3 { get; set; } 15 | } 16 | } -------------------------------------------------------------------------------- /ServiceLayer/Logger/TraceIndentGeneric.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | namespace ServiceLayer.Logger 5 | { 6 | public class TraceIndentGeneric : TraceIdentBaseDto 7 | { 8 | public TraceIndentGeneric(string traceIdentifier, T result) 9 | : base(traceIdentifier) 10 | { 11 | Result = result; 12 | } 13 | 14 | public T Result { get; private set; } 15 | } 16 | } -------------------------------------------------------------------------------- /ServiceLayer/BookServices/DropdownTuple.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | namespace ServiceLayer.BookServices 5 | { 6 | public class DropdownTuple 7 | { 8 | public string Value { get; set; } 9 | 10 | public string Text { get; set; } 11 | 12 | public override string ToString() 13 | { 14 | return $"{nameof(Value)}: {Value}, {nameof(Text)}: {Text}"; 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /Test/Chapter06Listings/BookNotSafe.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System.Collections.Generic; 5 | 6 | namespace Test.Chapter06Listings 7 | { 8 | public class BookNotSafe 9 | { 10 | public int Id { get; set; } 11 | public ICollection Reviews { get; set; } 12 | 13 | public BookNotSafe() 14 | { 15 | Reviews = new List(); 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /BookApp/Controllers/LoggerController.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using Microsoft.AspNetCore.Mvc; 5 | using ServiceLayer.Logger; 6 | 7 | namespace BookApp.Controllers 8 | { 9 | public class LoggerController : Controller 10 | { 11 | [HttpGet] 12 | public JsonResult GetLog(string traceIdentifier) 13 | { 14 | return Json(HttpRequestLog.GetHttpRequestLog(traceIdentifier)); 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /ServiceLayer/AdminServices/IChangePriceOfferService.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System.ComponentModel.DataAnnotations; 5 | using DataLayer.EfClasses; 6 | 7 | namespace ServiceLayer.AdminServices 8 | { 9 | public interface IChangePriceOfferService 10 | { 11 | Book OrgBook { get; } 12 | 13 | PriceOffer GetOriginal(int id); 14 | ValidationResult AddRemovePriceOffer(PriceOffer promotion); 15 | } 16 | } -------------------------------------------------------------------------------- /Test/Chapter03Listings/EfClasses/TagCheckSet.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System.Collections.Generic; 5 | using System.ComponentModel.DataAnnotations; 6 | 7 | namespace Test.Chapter03Listings.EfClasses 8 | { 9 | public class TagCheckSet 10 | { 11 | [Key] 12 | [Required] 13 | [MaxLength(40)] 14 | public string TagId { get; set; } 15 | 16 | public ICollection Books { get; set; } 17 | } 18 | } -------------------------------------------------------------------------------- /Test/Chapter02Listings/Lazy1Review.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | namespace Test.Chapter02Listings 5 | { 6 | public class Lazy1Review 7 | { 8 | public int Id { get; set; } 9 | public int NumStars { get; set; } 10 | 11 | //----------------------------------------- 12 | //Relationships 13 | 14 | //I don't place a foreign key here, which means EF Core will provide a foreign key via shadow properties. 15 | } 16 | 17 | 18 | } -------------------------------------------------------------------------------- /Test/Chapter02Listings/Lazy2Review.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | namespace Test.Chapter02Listings 5 | { 6 | public class Lazy2Review 7 | { 8 | public int Id { get; set; } 9 | public int NumStars { get; set; } 10 | 11 | //----------------------------------------- 12 | //Relationships 13 | 14 | //I don't place a foreign key here, which means EF Core will provide a foreign key via shadow properties. 15 | } 16 | 17 | 18 | } -------------------------------------------------------------------------------- /Test/Chapter02Listings/LazyReview.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | namespace Test.Chapter02Listings 5 | { 6 | public class LazyReview 7 | { 8 | public int Id { get; set; } 9 | public int NumStars { get; set; } 10 | 11 | //----------------------------------------- 12 | //Relationships 13 | 14 | //I don't place a foreign key here, which means EF Core will provide a foreign key via shadow properties. 15 | } 16 | 17 | 18 | } -------------------------------------------------------------------------------- /BizLogic/GenericInterfaces/IBizActionAsync.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System.Collections.Immutable; 5 | using System.ComponentModel.DataAnnotations; 6 | using System.Threading.Tasks; 7 | 8 | namespace BizLogic.GenericInterfaces 9 | { 10 | public interface IBizActionAsync 11 | { 12 | IImmutableList Errors { get; } 13 | 14 | bool HasErrors { get; } 15 | 16 | Task ActionAsync(TIn dto); 17 | } 18 | } -------------------------------------------------------------------------------- /Test/Mocks/TransactBizActionDto.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | namespace Test.Mocks 5 | { 6 | public enum MockBizActionTransact2Modes { Ok, BizErrorPart1, BizErrorPart2, ThrowExceptionPart2 } 7 | 8 | public class TransactBizActionDto 9 | { 10 | public TransactBizActionDto(MockBizActionTransact2Modes mode) 11 | { 12 | Mode = mode; 13 | } 14 | 15 | public MockBizActionTransact2Modes Mode { get; private set; } 16 | } 17 | } -------------------------------------------------------------------------------- /MyFirstEfCoreApp/Author.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | namespace MyFirstEfCoreApp 5 | { 6 | public class Author 7 | { 8 | public int AuthorId { get; set; } //#D 9 | public string Name { get; set; } 10 | public string WebUrl { get; set; } 11 | } 12 | 13 | /******************************************************* 14 | #D This holds the Primary Key of the Author row in the database 15 | * ************************************************/ 16 | } -------------------------------------------------------------------------------- /Test/Mocks/FakeUserIdService.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using DataLayer.EfCode; 6 | 7 | namespace Test.Mocks 8 | { 9 | public class FakeUserIdService : IUserIdService 10 | { 11 | private readonly Guid _dataKey; 12 | 13 | public FakeUserIdService(Guid dataKey) 14 | { 15 | _dataKey = dataKey; 16 | } 17 | 18 | public Guid GetUserId() 19 | { 20 | return _dataKey; 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /BookApp/wwwroot/lib/jquery-validation-unobtrusive/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) .NET Foundation. All rights reserved. 2 | 3 | Licensed under the Apache License, Version 2.0 (the "License"); you may not use 4 | these files except in compliance with the License. You may obtain a copy of the 5 | License at 6 | 7 | http://www.apache.org/licenses/LICENSE-2.0 8 | 9 | Unless required by applicable law or agreed to in writing, software distributed 10 | under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR 11 | CONDITIONS OF ANY KIND, either express or implied. See the License for the 12 | specific language governing permissions and limitations under the License. 13 | -------------------------------------------------------------------------------- /BookApp/Controllers/BaseTraceController.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using Microsoft.AspNetCore.Mvc; 5 | using ServiceLayer.Logger; 6 | 7 | namespace BookApp.Controllers 8 | { 9 | public abstract class BaseTraceController : Controller 10 | { 11 | protected void SetupTraceInfo() 12 | { 13 | ViewData["TraceIdent"] = HttpContext.TraceIdentifier; 14 | ViewData["NumLogs"] = HttpRequestLog.GetHttpRequestLog(HttpContext.TraceIdentifier).RequestLogs.Count; 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /ServiceLayer/OrderServices/OrderListDto.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using ServiceLayer.CheckoutServices; 7 | 8 | namespace ServiceLayer.OrderServices 9 | { 10 | public class OrderListDto 11 | { 12 | public int OrderId { get; set; } 13 | 14 | public DateTime DateOrderedUtc { get; set; } 15 | 16 | public string OrderNumber => $"SO{OrderId:D6}"; 17 | 18 | public IEnumerable LineItems { get; set; } 19 | } 20 | } -------------------------------------------------------------------------------- /ServiceLayer/Logger/TraceIdentBaseDto.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | namespace ServiceLayer.Logger 5 | { 6 | public class TraceIdentBaseDto 7 | { 8 | public string TraceIdentifier { get; private set; } 9 | 10 | public int NumLogs { get; private set; } 11 | 12 | public TraceIdentBaseDto(string traceIdentifier) 13 | { 14 | TraceIdentifier = traceIdentifier; 15 | NumLogs = HttpRequestLog.GetHttpRequestLog(traceIdentifier).RequestLogs.Count; 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /Test/Chapter02Listings/LazyProxyContext.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using Microsoft.EntityFrameworkCore; 5 | 6 | namespace Test.Chapter02Listings 7 | { 8 | /// 9 | /// This uses Microsoft.EntityFrameworkCore.Proxies and virtual to do lazy loading 10 | /// 11 | public class LazyProxyContext : DbContext 12 | { 13 | public LazyProxyContext(DbContextOptions options) 14 | : base(options) { } 15 | 16 | public DbSet Books { get; set; } 17 | 18 | } 19 | } -------------------------------------------------------------------------------- /BizLogic/Orders/Part1ToPart2Dto.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System.Collections.Immutable; 5 | using DataLayer.EfClasses; 6 | 7 | namespace BizLogic.Orders 8 | { 9 | public class Part1ToPart2Dto 10 | { 11 | public Part1ToPart2Dto(IImmutableList lineItems, Order order) 12 | { 13 | LineItems = lineItems; 14 | Order = order; 15 | } 16 | 17 | public IImmutableList LineItems { get; private set; } 18 | 19 | public Order Order { get; private set; } 20 | } 21 | } -------------------------------------------------------------------------------- /ServiceLayer/CheckoutServices/CheckoutItemDto.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using DataLayer.EfClasses; 5 | 6 | namespace ServiceLayer.CheckoutServices 7 | { 8 | public class CheckoutItemDto 9 | { 10 | public int BookId { get; internal set; } 11 | 12 | public string Title { get; internal set; } 13 | 14 | public string AuthorsName { get; internal set; } 15 | 16 | public decimal BookPrice { get; internal set; } 17 | 18 | public string ImageUrl { get; set; } 19 | 20 | public short NumBooks { get; internal set; } 21 | } 22 | } -------------------------------------------------------------------------------- /BookApp/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:59382", 7 | "sslPort": 0 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "BookApp": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "applicationUrl": "http://localhost:5000", 22 | "environmentVariables": { 23 | "ASPNETCORE_ENVIRONMENT": "Development" 24 | } 25 | } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /Test/Chapter02Listings/LazyInjectContext.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using Microsoft.EntityFrameworkCore; 5 | 6 | namespace Test.Chapter02Listings 7 | { 8 | /// 9 | /// This uses injection of ILazyLoader into class to do lazy loading 10 | /// 11 | public class LazyInjectContext : DbContext 12 | { 13 | public LazyInjectContext(DbContextOptions options) 14 | : base(options) { } 15 | 16 | public DbSet BookLazy1s { get; set; } 17 | public DbSet BookLazy2s { get; set; } 18 | } 19 | } -------------------------------------------------------------------------------- /Test/Chapter03Listings/EfClasses/ReviewSetCheck.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | 5 | using System.ComponentModel.DataAnnotations; 6 | 7 | namespace Test.Chapter03Listings.EfClasses 8 | { 9 | public class ReviewSetCheck 10 | { 11 | [Key] 12 | public int ReviewId { get; set; } 13 | public string VoterName { get; set; } 14 | public int NumStars { get; set; } 15 | public string Comment { get; set; } 16 | 17 | //----------------------------------------- 18 | //Relationships 19 | 20 | public int BookId { get; set; } //#M 21 | } 22 | } -------------------------------------------------------------------------------- /Test/Chapter02Listings/BookLazyProxy.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System.Collections.Generic; 5 | using DataLayer.EfClasses; 6 | 7 | namespace Test.Chapter02Listings 8 | { 9 | /// 10 | /// This uses Microsoft.EntityFrameworkCore.Proxies and virtual to do lazy loading 11 | /// 12 | public class BookLazyProxy 13 | { 14 | public int Id { get; set; } 15 | 16 | //NOTE: all properties need to be virtual 17 | public virtual PriceOffer Promotion { get; set; } 18 | public virtual ICollection Reviews { get; set; } 19 | 20 | } 21 | } -------------------------------------------------------------------------------- /BizLogic/AppStart/NetCoreDiSetupExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using Microsoft.Extensions.DependencyInjection; 5 | using NetCore.AutoRegisterDi; 6 | 7 | namespace BizLogic.AppStart 8 | { 9 | public static class NetCoreDiSetupExtensions 10 | { 11 | public static void RegisterBizLogicDi(this IServiceCollection services) 12 | { 13 | services.RegisterAssemblyPublicNonGenericClasses() 14 | .AsPublicImplementedInterfaces(); 15 | 16 | //register any services that can't be handled by RegisterAssemblyPublicNonGenericClasses 17 | 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /BizDbAccess/AppStart/NetCoreDiSetupExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using Microsoft.Extensions.DependencyInjection; 5 | using NetCore.AutoRegisterDi; 6 | 7 | namespace BizDbAccess.AppStart 8 | { 9 | public static class NetCoreDiSetupExtensions 10 | { 11 | public static void RegisterBizDbAccessDi(this IServiceCollection services) 12 | { 13 | services.RegisterAssemblyPublicNonGenericClasses() 14 | .AsPublicImplementedInterfaces(); 15 | 16 | //register any services that can't be handled by RegisterAssemblyPublicNonGenericClasses 17 | 18 | } 19 | } 20 | } -------------------------------------------------------------------------------- /ServiceLayer/BookServices/BookListCombinedDto.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System.Collections.Generic; 5 | using ServiceLayer.Logger; 6 | 7 | namespace ServiceLayer.BookServices 8 | { 9 | public class BookListCombinedDto 10 | { 11 | public BookListCombinedDto(SortFilterPageOptions sortFilterPageData, IEnumerable booksList) 12 | { 13 | SortFilterPageData = sortFilterPageData; 14 | BooksList = booksList; 15 | } 16 | 17 | public SortFilterPageOptions SortFilterPageData { get; private set; } 18 | 19 | public IEnumerable BooksList { get; private set; } 20 | } 21 | } -------------------------------------------------------------------------------- /ServiceLayer/CheckoutServices/Concrete/BasketCookie.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using Microsoft.AspNetCore.Http; 5 | 6 | namespace ServiceLayer.CheckoutServices.Concrete 7 | { 8 | public class BasketCookie : CookieTemplate 9 | { 10 | public const string BasketCookieName = "EfCoreInAction2-basket"; 11 | 12 | public BasketCookie(IRequestCookieCollection cookiesIn, IResponseCookies cookiesOut = null) 13 | : base(BasketCookieName, cookiesIn, cookiesOut) 14 | { 15 | } 16 | 17 | protected override int ExpiresInThisManyDays => 200; //Make this last, as it holds the user id for checking orders 18 | } 19 | } -------------------------------------------------------------------------------- /Test/Chapter02Listings/BookHashContext.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using DataLayer.EfClasses; 5 | using Microsoft.EntityFrameworkCore; 6 | 7 | namespace Test.Chapter02Listings 8 | { 9 | public class BookHashContext : DbContext 10 | { 11 | public BookHashContext(DbContextOptions options) 12 | : base(options) 13 | { 14 | } 15 | 16 | public DbSet Books { get; set; } 17 | 18 | protected override void OnModelCreating(ModelBuilder modelBuilder) 19 | { 20 | modelBuilder.Entity() 21 | .HasKey(p => p.BookId); 22 | } 23 | } 24 | } -------------------------------------------------------------------------------- /BizLogic/Orders/PlaceOrderInDto.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Collections.Immutable; 6 | 7 | namespace BizLogic.Orders 8 | { 9 | public class PlaceOrderInDto 10 | { 11 | public PlaceOrderInDto(bool acceptTAndCs, Guid userId, IImmutableList lineItems) 12 | { 13 | AcceptTAndCs = acceptTAndCs; 14 | UserId = userId; 15 | LineItems = lineItems; 16 | } 17 | 18 | public bool AcceptTAndCs { get; private set; } 19 | 20 | public Guid UserId { get; private set; } 21 | 22 | public IImmutableList LineItems { get; private set; } 23 | } 24 | } -------------------------------------------------------------------------------- /DataLayer/EfCode/ValidationDbContextServiceProvider.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT licence. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using Microsoft.EntityFrameworkCore; 6 | 7 | namespace DataLayer.EfCode 8 | { 9 | public class ValidationDbContextServiceProvider : IServiceProvider 10 | { 11 | private readonly DbContext _currContext; 12 | 13 | public ValidationDbContextServiceProvider(DbContext currContext) 14 | { 15 | _currContext = currContext; 16 | } 17 | 18 | public object GetService(Type serviceType) 19 | { 20 | if (serviceType == typeof(DbContext)) 21 | { 22 | return _currContext; 23 | } 24 | return null; 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /DataLayer/EfClasses/Author.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System.Collections.Generic; 5 | 6 | namespace DataLayer.EfClasses 7 | { 8 | public class Author //#E 9 | { 10 | public int AuthorId { get; set; } 11 | public string Name { get; set; } 12 | 13 | //------------------------------ 14 | //Relationships 15 | 16 | public ICollection 17 | BooksLink { get; set; } //#F 18 | } 19 | 20 | /********************************************************* 21 | #E The Author class just contains the name of the author 22 | #F This points to, via the linking table, all the books the Author has participated in 23 | * *****************************************************/ 24 | } -------------------------------------------------------------------------------- /DataLayer/EfClasses/Review.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | namespace DataLayer.EfClasses 5 | { 6 | public class Review //#L 7 | { 8 | public int ReviewId { get; set; } 9 | public string VoterName { get; set; } 10 | public int NumStars { get; set; } 11 | public string Comment { get; set; } 12 | 13 | //----------------------------------------- 14 | //Relationships 15 | 16 | public int BookId { get; set; } //#M 17 | } 18 | 19 | /******************************************************* 20 | #L This holds customer reviews with their rating 21 | #M This foreign key holds the key of the book this review belongs to 22 | * *****************************************************/ 23 | } -------------------------------------------------------------------------------- /DataLayer/EfClasses/PriceOffer.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | namespace DataLayer.EfClasses 5 | { 6 | public class PriceOffer //#A 7 | { 8 | public int PriceOfferId { get; set; } 9 | public decimal NewPrice { get; set; } 10 | public string PromotionalText { get; set; } 11 | 12 | //----------------------------------------------- 13 | //Relationships 14 | 15 | public int BookId { get; set; } //#b 16 | } 17 | 18 | /*************************************************** 19 | #N The PriceOffer is designed to override the normal price. It is a One-to-ZeroOrOne relationhsip 20 | #O This foreign key links back to the book it should be applied to 21 | * *************************************************/ 22 | } -------------------------------------------------------------------------------- /BookApp/Views/Shared/Error.cshtml: -------------------------------------------------------------------------------- 1 | @model ErrorViewModel 2 | @{ 3 | ViewData["Title"] = "Error"; 4 | } 5 | 6 |

Error.

7 |

An error occurred while processing your request.

8 | 9 | @if (Model.ShowRequestId) 10 | { 11 |

12 | Request ID: @Model.RequestId 13 |

14 | } 15 | 16 |

Development Mode

17 |

18 | Swapping to Development environment will display more detailed information about the error that occurred. 19 |

20 |

21 | The Development environment shouldn't be enabled for deployed applications. 22 | It can result in displaying sensitive information from exceptions to end users. 23 | For local debugging, enable the Development environment by setting the ASPNETCORE_ENVIRONMENT environment variable to Development 24 | and restarting the app. 25 |

26 | -------------------------------------------------------------------------------- /Test/Chapter02Listings/BookHashReview.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using DataLayer.EfClasses; 7 | 8 | namespace Test.Chapter02Listings 9 | { 10 | public class BookHashReview 11 | { 12 | public int BookId { get; set; } 13 | public string Title { get; set; } 14 | public string Description { get; set; } 15 | public DateTime PublishedOn { get; set; } 16 | public string Publisher { get; set; } 17 | public decimal Price { get; set; } 18 | public string ImageUrl { get; set; } 19 | 20 | public bool SoftDeleted { get; set; } 21 | 22 | //----------------------------------------------- 23 | //relationships 24 | 25 | public HashSet Reviews { get; set; } 26 | } 27 | } -------------------------------------------------------------------------------- /Test/Chapter03Listings/EfClasses/Author.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System.Collections.Generic; 5 | using DataLayer.EfClasses; 6 | 7 | namespace Test.Chapter03Listings.EfClasses 8 | { 9 | public class Author //#E 10 | { 11 | public int AuthorId { get; set; } 12 | public string Name { get; set; } 13 | 14 | //------------------------------ 15 | //Relationships 16 | 17 | public ICollection 18 | BooksLink { get; set; } //#F 19 | } 20 | 21 | /********************************************************* 22 | #E The Author class just contains the name of the author 23 | #F This points to, via the linking table, all the books the Author has participated in 24 | * *****************************************************/ 25 | } -------------------------------------------------------------------------------- /Test/Chapter03Listings/EfCode/Chapter3DbContext.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using Microsoft.EntityFrameworkCore; 5 | using Test.Chapter03Listings.EfClasses; 6 | 7 | namespace Test.Chapter03Listings.EfCode 8 | { 9 | public class Chapter3DbContext : DbContext 10 | { 11 | public DbSet BookCheckSets { get; set; } 12 | 13 | public DbSet SingleEntities { get; set; } 14 | 15 | public Chapter3DbContext( 16 | DbContextOptions options) 17 | : base(options) 18 | {} 19 | 20 | protected override void OnModelCreating(ModelBuilder modelBuilder) //#E 21 | { 22 | modelBuilder.Entity() 23 | .HasKey(x => new { x.BookId, x.AuthorId }); 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /DataLayer/EfClasses/Order.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT licence. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | 7 | namespace DataLayer.EfClasses 8 | { 9 | public class Order 10 | { 11 | public int OrderId { get; set; } 12 | 13 | public DateTime DateOrderedUtc { get; set; } 14 | 15 | /// 16 | /// In this simple example the cookie holds a GUID for everyone that 17 | /// 18 | public Guid CustomerId { get; set; } 19 | 20 | // relationships 21 | 22 | public ICollection LineItems { get; set; } 23 | 24 | // Extra columns not used by EF 25 | 26 | public string OrderNumber => $"SO{OrderId:D6}"; 27 | 28 | public Order() 29 | { 30 | DateOrderedUtc = DateTime.UtcNow; 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /Test/Mocks/MockBizAction.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using BizLogic.GenericInterfaces; 5 | using DataLayer.EfClasses; 6 | using DataLayer.EfCode; 7 | 8 | namespace Test.Mocks 9 | { 10 | public interface IMockBizAction : IBizAction {} 11 | 12 | public class MockBizAction : BizActionErrors, IMockBizAction 13 | { 14 | private readonly EfCoreContext _context; 15 | 16 | public MockBizAction(EfCoreContext context) 17 | { 18 | _context = context; 19 | } 20 | 21 | public string Action(int intIn) 22 | { 23 | if (intIn < 0) 24 | AddError("The intInt is less than zero"); 25 | 26 | _context.Authors.Add(new Author {Name = "MockBizAction"}); 27 | 28 | return intIn.ToString(); 29 | 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /Test/Mocks/MockHttpCookieAccess.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System.Collections.Generic; 5 | using Microsoft.AspNetCore.Http; 6 | 7 | namespace Test.Mocks 8 | { 9 | public class MockHttpCookieAccess 10 | { 11 | private readonly FakeResponseCookies _fakeResponse = new FakeResponseCookies(); 12 | 13 | public MockHttpCookieAccess(string cookieName = null, string cookieContent = null) 14 | { 15 | CookiesIn = new FakeRequestCookieCollection(cookieName, cookieContent); 16 | CookiesOut = _fakeResponse; 17 | } 18 | 19 | public IRequestCookieCollection CookiesIn { get; private set; } 20 | 21 | public IResponseCookies CookiesOut { get; private set; } 22 | 23 | public List> ResponseCookieValues => _fakeResponse.Responses; 24 | } 25 | } -------------------------------------------------------------------------------- /BookApp/Views/Admin/ChangePubDate.cshtml: -------------------------------------------------------------------------------- 1 | @model ServiceLayer.AdminServices.ChangePubDateDto 2 | @{ 3 | ViewBag.Title = "title"; 4 | } 5 | 6 |

Change Publication Date

7 |
8 | 9 | 10 | 11 |
12 |
13 | 14 |
15 |
16 | @Model.Title 17 |
18 |
19 | 20 |
21 |
22 | 23 |
24 | 25 |
26 | 27 |
28 |
29 | 30 |
31 |
32 | 33 |
34 | -------------------------------------------------------------------------------- /BizLogic/GenericInterfaces/IBizAction.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System.Collections.Immutable; 5 | using System.ComponentModel.DataAnnotations; 6 | 7 | namespace BizLogic.GenericInterfaces 8 | { 9 | public interface IBizAction//#A 10 | { 11 | IImmutableList 12 | Errors { get; } //#B 13 | 14 | bool HasErrors { get; } //#B 15 | TOut Action(TIn dto); //#C 16 | } 17 | //0123456789|123456789|123456789|123456789|123456789|123456789|123456789|xxxxx! 18 | /**************************************************** 19 | #A The BizAction uses the TIn and an TOut to define of the Action method 20 | #B This returns the error information from the business logic 21 | #C This is the action that the BizRunner will call 22 | * *************************************************/ 23 | } -------------------------------------------------------------------------------- /MyFirstEfCoreApp/Book.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System; 5 | 6 | namespace MyFirstEfCoreApp 7 | { 8 | public class Book 9 | { 10 | public int BookId { get; set; } //#A 11 | 12 | public string Title { get; set; } 13 | public string Description { get; set; } 14 | public DateTime PublishedOn { get; set; } 15 | public int AuthorId { get; set; } //#B 16 | 17 | 18 | public Author Author { get; set; } //#C 19 | } 20 | 21 | /********************************************************* 22 | #A This holds the Primary Key of the Books row in the database 23 | #B This holds the Foreign Key which references the Author row holding the name of the author 24 | #C EF Core will create a link to the Author class using the AuthroId foreign key 25 | * **********************************************************/ 26 | } -------------------------------------------------------------------------------- /Test/Mocks/MockBizActionAsync.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System.Threading.Tasks; 5 | using BizLogic.GenericInterfaces; 6 | using DataLayer.EfClasses; 7 | using DataLayer.EfCode; 8 | 9 | namespace Test.Mocks 10 | { 11 | public interface IMockBizActionAsync : IBizActionAsync {} 12 | 13 | public class MockBizActionAsync : BizActionErrors, IMockBizActionAsync 14 | { 15 | private readonly EfCoreContext _context; 16 | 17 | public MockBizActionAsync(EfCoreContext context) 18 | { 19 | _context = context; 20 | } 21 | 22 | public Task ActionAsync(int intIn) 23 | { 24 | if (intIn < 0) 25 | AddError("The intInt is less than zero"); 26 | 27 | _context.Authors.Add(new Author {Name = "MockBizAction"}); 28 | 29 | return Task.FromResult(intIn.ToString()); 30 | 31 | } 32 | } 33 | } -------------------------------------------------------------------------------- /Test/Mocks/MockBizActionPart1.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using BizLogic.GenericInterfaces; 5 | using DataLayer.EfClasses; 6 | using DataLayer.EfCode; 7 | 8 | namespace Test.Mocks 9 | { 10 | public interface IMockBizActionPart1 : IBizAction {} 11 | 12 | public class MockBizActionPart1 : BizActionErrors, IMockBizActionPart1 13 | { 14 | private readonly EfCoreContext _context; 15 | 16 | public MockBizActionPart1(EfCoreContext context) 17 | { 18 | _context = context; 19 | } 20 | 21 | public TransactBizActionDto Action(TransactBizActionDto dto) 22 | { 23 | if (dto.Mode == MockBizActionTransact2Modes.BizErrorPart1) 24 | AddError("Failed in Part1"); 25 | 26 | _context.Authors.Add(new Author {Name = "Part1"}); 27 | 28 | return dto; 29 | 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /Test/Chapter03Listings/EfClasses/BookCheckSet.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System.Collections.Generic; 5 | using System.ComponentModel.DataAnnotations; 6 | 7 | namespace Test.Chapter03Listings.EfClasses 8 | { 9 | public class BookCheckSet 10 | { 11 | public BookCheckSet() 12 | { 13 | Reviews = new List(); 14 | Tags = new List(); 15 | AuthorsLink = new List(); 16 | } 17 | 18 | [Key] 19 | public int BookId { get; set; } 20 | public string Title { get; set; } 21 | 22 | //----------------------------------------------- 23 | //relationships 24 | 25 | public ICollection Reviews { get; set; } 26 | 27 | public ICollection Tags { get; set; } 28 | 29 | public ICollection AuthorsLink { get; set; } 30 | } 31 | } -------------------------------------------------------------------------------- /ServiceLayer/AdminServices/ChangePubDateDto.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.ComponentModel.DataAnnotations; 6 | 7 | namespace ServiceLayer.AdminServices 8 | { 9 | public class ChangePubDateDto 10 | { 11 | public int BookId { get; set; } //#A 12 | public string Title { get; set; } //#B 13 | 14 | [DataType(DataType.Date)] //#C 15 | public DateTime PublishedOn { get; set; }//#C 16 | } 17 | /************************************************* 18 | #A This holds the primary key of the row we want to update. This makes finding the right entry quick and accurate 19 | #B We send over the title to show the user, so that they can be sure they are altering the right book 20 | #C This is the property we want to alter. We send out the current publication date and get back the changed publication date 21 | * ********************************************/ 22 | } -------------------------------------------------------------------------------- /Test/Chapter06Listings/EfManyExtension.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System.Collections.Generic; 5 | 6 | namespace Test.Chapter06Listings 7 | { 8 | public static class EfManyExtension 9 | { 10 | public static ManyTop AddManyTopWithRelationsToDb(this Chapter06Context context, int numRelations = 100) 11 | { 12 | var manyTop = new ManyTop(); 13 | manyTop.Collection1 = new List(); 14 | manyTop.Collection2 = new List(); 15 | manyTop.Collection3 = new List(); 16 | for (int i = 0; i < numRelations; i++) 17 | { 18 | manyTop.Collection1.Add(new Many1()); 19 | manyTop.Collection2.Add(new Many2()); 20 | manyTop.Collection3.Add(new Many3()); 21 | } 22 | 23 | context.Add(manyTop); 24 | context.SaveChanges(); 25 | 26 | return manyTop; 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /Test/UnitCommands/DeleteAllUnitTestDatabases.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT licence. See License.txt in the project root for license information. 3 | 4 | 5 | using TestSupport.Attributes; 6 | using TestSupport.EfHelpers; 7 | using Xunit.Abstractions; 8 | 9 | namespace Test.UnitCommands 10 | { 11 | public class DeleteAllUnitTestDatabases 12 | { 13 | private readonly ITestOutputHelper _output; 14 | 15 | public DeleteAllUnitTestDatabases(ITestOutputHelper output) 16 | { 17 | _output = output; 18 | } 19 | 20 | //Run this method to wipe ALL the test database for the current branch 21 | //You need to run it in debug mode - that stops it being run when you "run all" unit tests 22 | [RunnableInDebugOnly] 23 | public void DeleteAllTestDatabasesOk() 24 | { 25 | var numDeleted = DatabaseTidyHelper 26 | .DeleteAllUnitTestDatabases(); 27 | _output.WriteLine("This deleted {0} databases.", numDeleted); 28 | } 29 | } 30 | } -------------------------------------------------------------------------------- /Test/Chapter06Listings/Chapter06Context.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using Microsoft.EntityFrameworkCore; 5 | 6 | namespace Test.Chapter06Listings 7 | { 8 | public class Chapter06Context : DbContext 9 | { 10 | public DbSet Employees { get; set; } 11 | public DbSet ManyTops { get; set; } 12 | 13 | public DbSet Books { get; set; } 14 | 15 | public DbSet OnePrincipals { get; set; } 16 | public DbSet OneDependents { get; set; } 17 | 18 | public Chapter06Context(DbContextOptions options) 19 | : base(options) { } 20 | 21 | protected override void OnModelCreating(ModelBuilder modelBuilder) 22 | { 23 | modelBuilder.Entity() 24 | .HasMany(x => x.WorksForMe) 25 | .WithOne(x => x.Manager) 26 | .HasForeignKey(x => x.ManagerEmployeeId); 27 | } 28 | } 29 | } -------------------------------------------------------------------------------- /ServiceLayer/ServiceLayer.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /DataLayer/EfClasses/BookAuthor.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | namespace DataLayer.EfClasses 5 | { 6 | public class BookAuthor //#G 7 | { 8 | public int BookId { get; set; } //#H 9 | public int AuthorId { get; set; } //#H 10 | public byte Order { get; set; } //#I 11 | 12 | //----------------------------- 13 | //Relationships 14 | 15 | public Book Book { get; set; } //#J 16 | public Author Author { get; set; } //#K 17 | } 18 | 19 | /************************************************** 20 | #G The BookAuthor class is the Many-to-Many linking table between the Books and Authors tables 21 | #H The Primary Key is made up of the two keys of the Book and Author 22 | #I The order of the Authors in a book matters, so I use this to set the right order 23 | #J This is the link to the Book side of the relationship 24 | #K And this links to the Author side of the relationship 25 | * ***********************************************/ 26 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Jon P Smith - https://www.thereformedprogrammer.net/ 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /BookApp/Controllers/OrdersController.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using DataLayer.EfCode; 5 | using Microsoft.AspNetCore.Mvc; 6 | using ServiceLayer.OrderServices.Concrete; 7 | 8 | namespace BookApp.Controllers 9 | { 10 | public class OrdersController : BaseTraceController 11 | { 12 | private readonly EfCoreContext _context; 13 | 14 | public OrdersController(EfCoreContext context) 15 | { 16 | _context = context; 17 | } 18 | 19 | // GET: // 20 | public IActionResult Index() 21 | { 22 | var listService = new DisplayOrdersService(_context); 23 | SetupTraceInfo(); 24 | return View(listService.GetUsersOrders()); 25 | } 26 | 27 | public IActionResult ConfirmOrder(int orderId) 28 | { 29 | var detailService = new DisplayOrdersService(_context); 30 | SetupTraceInfo(); 31 | return View(detailService.GetOrderDetail(orderId)); 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /DataLayer/QueryObjects/GenericPaging.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Linq; 6 | 7 | namespace DataLayer.QueryObjects 8 | { 9 | public static class GenericPaging 10 | { 11 | public static IQueryable Page( 12 | this IQueryable query, 13 | int pageNumZeroStart, int pageSize) 14 | { 15 | if (pageSize == 0) 16 | throw new ArgumentOutOfRangeException 17 | (nameof(pageSize), "pageSize cannot be zero."); 18 | 19 | if (pageNumZeroStart != 0) 20 | query = query 21 | .Skip(pageNumZeroStart * pageSize); //#A 22 | 23 | return query.Take(pageSize); //#B 24 | } 25 | 26 | /*************************************************************** 27 | #A It skips the correct number of pages 28 | #B It then takes the number for this page size 29 | * ************************************************************/ 30 | } 31 | } -------------------------------------------------------------------------------- /MyFirstEfCoreApp/AppDbContext.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using Microsoft.EntityFrameworkCore; 5 | 6 | namespace MyFirstEfCoreApp 7 | { 8 | public class AppDbContext : DbContext 9 | { 10 | private const string ConnectionString = //#A 11 | @"Server=(localdb)\mssqllocaldb; 12 | Database=MyFirstEfCoreDb; 13 | Trusted_Connection=True"; 14 | 15 | public DbSet Books { get; set; } 16 | 17 | protected override void OnConfiguring( 18 | DbContextOptionsBuilder optionsBuilder) 19 | { 20 | optionsBuilder.UseSqlServer(ConnectionString); //#B 21 | } 22 | } 23 | 24 | /******************************************************** 25 | #A The connection string is used by the SQL Server database provider to find the database 26 | #B Using the SQL Server database provider’s UseSqlServer command sets up the options ready for creating the applications’s DBContext 27 | ********************************************************/ 28 | } -------------------------------------------------------------------------------- /BookApp/wwwroot/lib/jquery-validation/LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | ===================== 3 | 4 | Copyright Jörn Zaefferer 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /Test/UnitTests/TestAspNetCore/TestCalculateReviewsToMatch.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System.Linq; 5 | using ServiceLayer.DatabaseServices.Concrete; 6 | using Xunit; 7 | using Xunit.Extensions.AssertExtensions; 8 | 9 | namespace Test.UnitTests.TestAspNetCore 10 | { 11 | public class TestCalculateReviewsToMatch 12 | { 13 | [Theory] 14 | [InlineData(5)] 15 | [InlineData(5, 1)] 16 | [InlineData(5, 1, 1)] 17 | [InlineData(5, 1, 1, 1, 1)] 18 | [InlineData(5, 1, 5)] 19 | [InlineData(3, 4, 3)] 20 | public void TestCalcReviewsOk(params int[] voteVals) 21 | { 22 | //SETUP 23 | var avgRating = voteVals.Average(); 24 | var numVotes = voteVals.Length; 25 | 26 | //ATTEMPT 27 | var reviews = BookJsonLoader.CalculateReviewsToMatch(avgRating, numVotes); 28 | 29 | //VERIFY 30 | reviews.Count.ShouldEqual(numVotes); 31 | reviews.Average(x => x.NumStars).ShouldEqual(avgRating); 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /BookApp/wwwroot/lib/bootstrap/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2011-2018 Twitter, Inc. 4 | Copyright (c) 2011-2018 The Bootstrap Authors 5 | 6 | Permission is hereby granted, free of charge, to any person obtaining a copy 7 | of this software and associated documentation files (the "Software"), to deal 8 | in the Software without restriction, including without limitation the rights 9 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 10 | copies of the Software, and to permit persons to whom the Software is 11 | furnished to do so, subject to the following conditions: 12 | 13 | The above copyright notice and this permission notice shall be included in 14 | all copies or substantial portions of the Software. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 17 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 18 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 19 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 20 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 21 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 22 | THE SOFTWARE. 23 | -------------------------------------------------------------------------------- /Test/UnitTests/TestServiceLayer/Ch03_CalculateReviewsToMatch.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System.Linq; 5 | using ServiceLayer.DatabaseServices.Concrete; 6 | using Xunit; 7 | using Xunit.Extensions.AssertExtensions; 8 | 9 | namespace Test.UnitTests.TestServiceLayer 10 | { 11 | public class Ch03_CalculateReviewsToMatch 12 | { 13 | [Theory] 14 | [InlineData(5)] 15 | [InlineData(5,1)] 16 | [InlineData(5, 1, 1)] 17 | [InlineData(5, 1, 1, 1, 1)] 18 | [InlineData(5, 1, 5)] 19 | [InlineData(3, 4, 3)] 20 | public void TestCalcReviewsOk(params int [] voteVals) 21 | { 22 | //SETUP 23 | var avgRating = voteVals.Average(); 24 | var numVotes = voteVals.Length; 25 | 26 | //ATTEMPT 27 | var reviews = BookJsonLoader.CalculateReviewsToMatch(avgRating, numVotes); 28 | 29 | //VERIFY 30 | reviews.Count.ShouldEqual(numVotes); 31 | reviews.Average(x => x.NumStars).ShouldEqual(avgRating); 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /Test/Chapter03Listings/EfClasses/BookAuthorCheckSet.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | namespace Test.Chapter03Listings.EfClasses 5 | { 6 | public class BookAuthorCheckSet //#G 7 | { 8 | public int BookId { get; set; } //#H 9 | public int AuthorId { get; set; } //#H 10 | public byte Order { get; set; } //#I 11 | 12 | //----------------------------- 13 | //Relationships 14 | 15 | public BookCheckSet BookCheckSet { get; set; } //#J 16 | public Author Author { get; set; } //#K 17 | } 18 | 19 | /************************************************** 20 | #G The BookAuthor class is the Many-to-Many linking table between the Books and Authors tables 21 | #H The Primary Key is made up of the two keys of the BookCheckSet and Author 22 | #I The order of the Authors in a book matters, so I use this to set the right order 23 | #J This is the link to the BookCheckSet side of the relationship 24 | #K And this links to the Author side of the relationship 25 | * ***********************************************/ 26 | } -------------------------------------------------------------------------------- /Test/Chapter05Listings/ExampleSeed.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using DataLayer.EfCode; 7 | using Test.TestHelpers; 8 | 9 | namespace Test.Chapter05Listings 10 | { 11 | public static class ExampleSeed 12 | { 13 | public static async Task SeedDatabaseAsync //#A 14 | (this EfCoreContext context) //#A 15 | { 16 | if (context.Books.Any()) return;//#B 17 | 18 | context.Books.AddRange( //#C 19 | EfTestData.CreateFourBooks());//#C 20 | await context.SaveChangesAsync(); //#D 21 | } 22 | 23 | /************************************************ 24 | #A This is an extension method that takes in the application's DbContext 25 | #B If there are existing books I return, as I don't need to add any 26 | #C The database has no books, so I seed it, which in this case I add the default books 27 | #D SaveChangesAsync is called to update the database 28 | * ************************************************/ 29 | } 30 | } -------------------------------------------------------------------------------- /Test/Mocks/MockBizActionPart2.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using BizLogic.GenericInterfaces; 6 | using DataLayer.EfClasses; 7 | using DataLayer.EfCode; 8 | 9 | namespace Test.Mocks 10 | { 11 | public interface IMockBizActionPart2 : IBizAction {} 12 | 13 | public class MockBizActionPart2 : BizActionErrors, IMockBizActionPart2 14 | { 15 | private readonly EfCoreContext _context; 16 | 17 | public MockBizActionPart2(EfCoreContext context) 18 | { 19 | _context = context; 20 | } 21 | 22 | public TransactBizActionDto Action(TransactBizActionDto dto) 23 | { 24 | if (dto.Mode == MockBizActionTransact2Modes.BizErrorPart2) 25 | AddError("Failed in Part2"); 26 | if (dto.Mode == MockBizActionTransact2Modes.ThrowExceptionPart2) 27 | throw new InvalidOperationException("I have thrown an exception."); 28 | 29 | _context.Authors.Add(new Author {Name = "Part2"}); 30 | 31 | return dto; 32 | 33 | } 34 | } 35 | } -------------------------------------------------------------------------------- /Test/Chapter06Listings/Employee.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Collections.ObjectModel; 7 | using System.ComponentModel.DataAnnotations.Schema; 8 | 9 | namespace Test.Chapter06Listings 10 | { 11 | [Flags] 12 | public enum Roles {NotSet, Development = 1, Management = 2, Sales = 4, Finance = 8, HR = 16} 13 | 14 | public class Employee 15 | { 16 | private Employee() {} //needed for EF Core 17 | 18 | public Employee(string name, Roles whatTheyDo, Employee manager) 19 | { 20 | Name = name; 21 | WhatTheyDo = whatTheyDo; 22 | Manager = manager; 23 | } 24 | 25 | public int EmployeeId { get; set; } 26 | public string Name { get; set; } 27 | public Roles WhatTheyDo { get; set; } 28 | 29 | //---------------------------------------- 30 | //relationships 31 | 32 | public int? ManagerEmployeeId { get; set; } 33 | public Employee Manager { get; set; } 34 | 35 | public IList WorksForMe { get; set; } 36 | } 37 | } -------------------------------------------------------------------------------- /ServiceLayer/Logger/LogParts.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System.Text; 5 | using Microsoft.EntityFrameworkCore.Storage; 6 | using Microsoft.Extensions.Logging; 7 | using Newtonsoft.Json; 8 | using Newtonsoft.Json.Converters; 9 | 10 | namespace ServiceLayer.Logger 11 | { 12 | public class LogParts 13 | { 14 | private const string EfCoreEventIdStartWith = "Microsoft.EntityFrameworkCore"; 15 | 16 | public LogParts(LogLevel logLevel, EventId eventId, string eventString) 17 | { 18 | LogLevel = logLevel; 19 | EventId = eventId; 20 | EventString = eventString; 21 | } 22 | 23 | [JsonConverter(typeof(StringEnumConverter))] 24 | public LogLevel LogLevel { get; private set; } 25 | 26 | public EventId EventId { get; private set; } 27 | 28 | public string EventString { get; private set; } 29 | 30 | public bool IsDb => EventId.Name?.StartsWith(EfCoreEventIdStartWith) ?? false; 31 | 32 | public override string ToString() 33 | { 34 | return $"{LogLevel}: {EventString}"; 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /ServiceLayer/DatabaseServices/Concrete/BookInfoJson.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System.Collections.Generic; 5 | 6 | namespace ServiceLayer.DatabaseServices.Concrete 7 | { 8 | public class BookInfoJson 9 | { 10 | public string title { get; set; } 11 | 12 | public List authors { get; set; } 13 | 14 | public string publisher { get; set; } 15 | 16 | public string publishedDate { get; set; } 17 | 18 | public string description { get; set; } 19 | 20 | public int pageCount { get; set; } 21 | 22 | public List categories { get; set; } 23 | 24 | public double? averageRating { get; set; } 25 | 26 | public int? ratingsCount { get; set; } 27 | 28 | public string imageLinksThumbnail { get; set; } 29 | 30 | public string saleInfoCountry { get; set; } 31 | 32 | public bool? saleInfoForSale { get; set; } 33 | 34 | public string saleInfoBuyLink { get; set; } 35 | 36 | public double? saleInfoListPriceAmount { get; set; } 37 | 38 | public string saleInfoListPriceCurrencyCode { get; set; } 39 | } 40 | } -------------------------------------------------------------------------------- /Test/Mocks/FakeResponseCookies.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using Microsoft.AspNetCore.Http; 7 | 8 | namespace Test.Mocks 9 | { 10 | public class FakeResponseCookies : IResponseCookies 11 | { 12 | public List> Responses { get; private set; } = 13 | new List>(); 14 | 15 | public void Append(string key, string value) 16 | { 17 | Responses.Add(new KeyValuePair(key, value)); 18 | } 19 | 20 | public void Append(string key, string value, CookieOptions options) 21 | { 22 | Responses.Add(new KeyValuePair(key, $"{value}; {options.ToString()}")); 23 | } 24 | 25 | public void Delete(string key) 26 | { 27 | var found = Responses.SingleOrDefault(x => x.Key == key); 28 | if (found.Key == key) 29 | Responses.Remove(found); 30 | } 31 | 32 | public void Delete(string key, CookieOptions options) 33 | { 34 | throw new System.NotImplementedException(); 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /ServiceLayer/BizRunners/RunnerWriteDbAsync.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System.Collections.Immutable; 5 | using System.ComponentModel.DataAnnotations; 6 | using System.Linq; 7 | using System.Threading.Tasks; 8 | using BizLogic.GenericInterfaces; 9 | using Microsoft.EntityFrameworkCore; 10 | 11 | namespace ServiceLayer.BizRunners 12 | { 13 | public class RunnerWriteDbAsync 14 | { 15 | private readonly IBizActionAsync _actionClass; 16 | private readonly DbContext _context; 17 | 18 | public RunnerWriteDbAsync(IBizActionAsync actionClass, DbContext context) 19 | { 20 | _context = context; 21 | _actionClass = actionClass; 22 | } 23 | 24 | public IImmutableList Errors => _actionClass.Errors; 25 | public bool HasErrors => _actionClass.HasErrors; 26 | 27 | public async Task RunActionAsync(TIn dataIn) 28 | { 29 | var result = await _actionClass.ActionAsync(dataIn).ConfigureAwait(false); 30 | if (!HasErrors) 31 | await _context.SaveChangesAsync(); 32 | 33 | return result; 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /Test/UnitTests/TestDataLayer/Ch03_SimpleUpdateSql.sql: -------------------------------------------------------------------------------- 1 | 2 | SELECT TOP(2) //#A 3 | 4 | 5 | [p].[BookId], //#B 6 | [p].[Description], //#B 7 | [p].[ImageUrl], //#B 8 | [p].[Price], //#B 9 | [p].[PublishedOn], //#B 10 | [p].[Publisher], //#B 11 | [p].[Title] //#B 12 | FROM [Books] AS [p] //#B 13 | WHERE [p].[Title] = N'Quantum Networking' //#C 14 | 15 | SET NOCOUNT ON; 16 | UPDATE [Books] //#D 17 | SET [PublishedOn] = @p0 //#E 18 | 19 | WHERE [BookId] = @p1; //#F 20 | SELECT @@ROWCOUNT; //#G 21 | #A This reads up to 2 entries from the Books table - we asked for a single item in out code, but this makes sure it fails if there is more than one row that fits 22 | #B The read loads all the columns in the table 23 | #C This is our LINQ Where method. It picks out the entry by its title 24 | #D This is the SQL UPDATE command in this case on the Books table 25 | #E Because EF Core's DetectChanges method found only the PublishedOn property had changed it can target that column in the table 26 | #F EF Core used the primary key from the original book to uniquely select the row it wants to update 27 | #G Finally it sends back the number of rows that were inserted in this transaction. SaveChanges returns this integer, but normally I ignore it. 28 | 29 | -------------------------------------------------------------------------------- /.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "tasks": [ 4 | { 5 | "label": "build", 6 | "command": "dotnet", 7 | "type": "process", 8 | "args": [ 9 | "build", 10 | "${workspaceFolder}/BookApp/BookApp.csproj", 11 | "/property:GenerateFullPaths=true", 12 | "/consoleloggerparameters:NoSummary" 13 | ], 14 | "problemMatcher": "$msCompile" 15 | }, 16 | { 17 | "label": "publish", 18 | "command": "dotnet", 19 | "type": "process", 20 | "args": [ 21 | "publish", 22 | "${workspaceFolder}/BookApp/BookApp.csproj", 23 | "/property:GenerateFullPaths=true", 24 | "/consoleloggerparameters:NoSummary" 25 | ], 26 | "problemMatcher": "$msCompile" 27 | }, 28 | { 29 | "label": "watch", 30 | "command": "dotnet", 31 | "type": "process", 32 | "args": [ 33 | "watch", 34 | "run", 35 | "${workspaceFolder}/BookApp/BookApp.csproj", 36 | "/property:GenerateFullPaths=true", 37 | "/consoleloggerparameters:NoSummary" 38 | ], 39 | "problemMatcher": "$msCompile" 40 | } 41 | ] 42 | } -------------------------------------------------------------------------------- /BookApp/Views/Admin/AddBookReview.cshtml: -------------------------------------------------------------------------------- 1 | @model DataLayer.EfClasses.Review 2 | 3 |

Add a book review

4 | 5 | 6 |
7 | 8 | 9 | 10 |
11 |
12 | 13 |
14 |
15 | @ViewData["BookTitle"] 16 |
17 |
18 | 19 |
20 |
21 | 22 |
23 | 24 |
25 | 26 |
27 |
28 | 29 |
30 | 31 |
32 | 33 |
34 |
35 | 36 |
37 | 38 |
39 | 40 |
41 |
42 | 43 |
44 |
45 | 46 |
47 | -------------------------------------------------------------------------------- /BizLogic/Orders/Concrete/PlaceOrderPart1.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System.Linq; 5 | using BizDbAccess.Orders; 6 | using BizLogic.GenericInterfaces; 7 | using DataLayer.EfClasses; 8 | 9 | namespace BizLogic.Orders.Concrete 10 | { 11 | public class PlaceOrderPart1 : BizActionErrors, IPlaceOrderPart1 12 | { 13 | private readonly IPlaceOrderDbAccess _dbAccess; 14 | 15 | public PlaceOrderPart1(IPlaceOrderDbAccess dbAccess) 16 | { 17 | _dbAccess = dbAccess; 18 | } 19 | 20 | public Part1ToPart2Dto Action(PlaceOrderInDto dto) 21 | { 22 | if (!dto.AcceptTAndCs) 23 | AddError("You must accept the T&Cs to place an order."); 24 | 25 | if (!dto.LineItems.Any()) 26 | AddError("No items in your basket."); 27 | 28 | var order = new Order 29 | { 30 | CustomerId = dto.UserId 31 | }; 32 | 33 | if (!HasErrors) 34 | _dbAccess.Add(order); 35 | 36 | return new Part1ToPart2Dto(dto.LineItems, order); 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /Test/UnitTests/TestServiceLayer/Ch04_RunnerWriteDb.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System.Linq; 5 | using DataLayer.EfCode; 6 | using ServiceLayer.BizRunners; 7 | using Test.Mocks; 8 | using TestSupport.EfHelpers; 9 | using Xunit; 10 | using Xunit.Extensions.AssertExtensions; 11 | 12 | namespace Test.UnitTests.TestServiceLayer 13 | { 14 | public class Ch04_RunnerWriteDb 15 | { 16 | [Theory] 17 | [InlineData(1, false)] 18 | [InlineData(-1, true)] 19 | public void RunAction(int input, bool hasErrors) 20 | { 21 | //SETUP 22 | var options = SqliteInMemory.CreateOptions(); 23 | using (var context = new EfCoreContext(options)) 24 | { 25 | context.Database.EnsureCreated(); 26 | var action = new MockBizAction(context); 27 | var runner = new RunnerWriteDb(action, context); 28 | 29 | //ATTEMPT 30 | var output = runner.RunAction(input); 31 | 32 | //VERIFY 33 | output.ShouldEqual(input.ToString()); 34 | runner.HasErrors.ShouldEqual(hasErrors); 35 | context.Authors.Count().ShouldEqual(hasErrors ? 0 : 1); 36 | } 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /ServiceLayer/AppStart/NetCoreDiSetupExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using Microsoft.Extensions.DependencyInjection; 5 | using NetCore.AutoRegisterDi; 6 | 7 | namespace ServiceLayer.AppStart 8 | { 9 | public static class NetCoreDiSetupExtensions //#A 10 | { 11 | public static void RegisterServiceLayerDi //#B 12 | (this IServiceCollection services) //#C 13 | { 14 | services.RegisterAssemblyPublicNonGenericClasses() //#D 15 | .AsPublicImplementedInterfaces(); //#E 16 | 17 | //#F 18 | } 19 | } 20 | /**************************************************************************** 21 | #A I create a static class to hold my extension 22 | #B This class is in the ServiceLayer, so I give the method a name with that Assembly name in it 23 | #C The NetCore.AutoRegisterDi library understands NET Core DI so you can access the IServiceCollection interface 24 | #D Calling the RegisterAssemblyPublicNonGenericClasses method without a parameter means it scans the calling assembly 25 | #E This method will register all the public classes with interfaces with a Transient lifetime 26 | #F This is for hand-coded registrations that NetCore.AutoRegisterDi can't do, e.g. generic classes 27 | *************************************************************/ 28 | } -------------------------------------------------------------------------------- /Test/UnitTests/TestServiceLayer/Ch02_BookJsonLoader.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System.Linq; 5 | using ServiceLayer.DatabaseServices.Concrete; 6 | using TestSupport.Helpers; 7 | using Xunit; 8 | using Xunit.Extensions.AssertExtensions; 9 | 10 | namespace Test.UnitTests.TestServiceLayer 11 | { 12 | public class Ch02_BookJsonLoader 13 | { 14 | [Fact] 15 | public void TestBookLoadBuildReviewsOk() 16 | { 17 | //SETUP 18 | const string searchFile = "JsonBooks01*.json"; 19 | var testDataDir = TestData.GetTestDataDir(); 20 | 21 | //ATTEMPT 22 | var books = BookJsonLoader.LoadBooks(testDataDir, searchFile); 23 | 24 | //VERIFY 25 | var expectedAveVotes = new[] {5.0, 3.0, 4.0, 4.5}; 26 | books.Select(x => x.Reviews.Average(y => y.NumStars)).ShouldEqual(expectedAveVotes); 27 | } 28 | 29 | [Fact] 30 | public void TestBookLoadOk() 31 | { 32 | //SETUP 33 | const string searchFile = "JsonBooks01*.json"; 34 | var testDataDir = TestData.GetTestDataDir(); 35 | 36 | //ATTEMPT 37 | var books = BookJsonLoader.LoadBooks(testDataDir, searchFile); 38 | 39 | //VERIFY 40 | books.Count().ShouldEqual(4); 41 | } 42 | } 43 | } -------------------------------------------------------------------------------- /BookApp/Views/Orders/OneOrderPartial.cshtml: -------------------------------------------------------------------------------- 1 | @model ServiceLayer.OrderServices.OrderListDto 2 | 3 |
4 |
5 | Order @Model.OrderNumber placed on @Model.DateOrderedUtc.ToString("f") 6 |
7 |
8 |
9 | 10 | @{ 11 | foreach (var lineItem in Model.LineItems) 12 | { 13 |
14 |
15 | 16 |
17 |
18 |

19 | @lineItem.Title 20 |

21 | by @lineItem.AuthorsName 22 |
23 | 24 |
25 |
26 | 27 | $@($"{lineItem.BookPrice:F}") 28 | 29 |
30 |
31 |
32 |
@lineItem.NumBooks
33 |
34 | 35 |
36 |
37 | } 38 | } 39 | 40 |
41 | 42 |
43 |

44 | Total $@($"{Model.LineItems.Sum(x => x.BookPrice * x.NumBooks):F}") 45 |

46 |
47 |
48 | 49 | 50 | -------------------------------------------------------------------------------- /Test/UnitTests/TestServiceLayer/Ch04_RunnersAsync.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using DataLayer.EfCode; 7 | using ServiceLayer.BizRunners; 8 | using Test.Mocks; 9 | using TestSupport.EfHelpers; 10 | using Xunit; 11 | using Xunit.Extensions.AssertExtensions; 12 | 13 | namespace Test.UnitTests.TestServiceLayer 14 | { 15 | public class Ch04_RunnersAsync 16 | { 17 | [Theory] 18 | [InlineData(1, false)] 19 | [InlineData(-1, true)] 20 | public async Task RunActionAsync(int input, bool hasErrors) 21 | { 22 | //SETUP 23 | var options = SqliteInMemory.CreateOptions(); 24 | using (var context = new EfCoreContext(options)) 25 | { 26 | context.Database.EnsureCreated(); 27 | var action = new MockBizActionAsync(context); 28 | var runner = new RunnerWriteDbAsync(action, context); 29 | 30 | //ATTEMPT 31 | var output = await runner.RunActionAsync(input); 32 | 33 | //VERIFY 34 | output.ShouldEqual(input.ToString()); 35 | runner.HasErrors.ShouldEqual(hasErrors); 36 | context.Authors.Count().ShouldEqual(hasErrors ? 0 : 1); 37 | } 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /Test/Chapter02Listings/BookLazy2.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Runtime.CompilerServices; 7 | using DataLayer.EfClasses; 8 | 9 | namespace Test.Chapter02Listings 10 | { 11 | public class BookLazy2 12 | { 13 | public BookLazy2() { } 14 | 15 | private BookLazy2(Action lazyLoader) 16 | { 17 | LazyLoader = lazyLoader; 18 | } 19 | private Action LazyLoader { get; set; } 20 | 21 | 22 | public int Id { get; set; } 23 | 24 | public PriceOffer Promotion { get; set; } 25 | 26 | private ICollection _reviews; 27 | public ICollection Reviews 28 | { 29 | get => LazyLoader.Load(this, ref _reviews); 30 | set => _reviews = value; 31 | } 32 | } 33 | 34 | public static class PocoLoadingExtensions 35 | { 36 | public static TRelated Load( 37 | this Action loader, 38 | object entity, 39 | ref TRelated navigationField, 40 | [CallerMemberName] string navigationName = null) 41 | where TRelated : class 42 | { 43 | loader?.Invoke(entity, navigationName); 44 | 45 | return navigationField; 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /BookApp/Program.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System.Threading.Tasks; 5 | using BookApp.HelperExtensions; 6 | using Microsoft.AspNetCore.Hosting; 7 | using Microsoft.Extensions.DependencyInjection; 8 | using Microsoft.Extensions.Hosting; 9 | using Microsoft.Extensions.Logging; 10 | using ServiceLayer.BackgroundServices; 11 | 12 | namespace BookApp 13 | { 14 | public class Program 15 | { 16 | public static async Task Main(string[] args) 17 | { 18 | var host = CreateHostBuilder(args).Build(); 19 | //This migrates the database and adds any seed data as required 20 | await host.SetupDatabaseAsync(); 21 | await host.RunAsync(); 22 | } 23 | 24 | public static IHostBuilder CreateHostBuilder(string[] args) => 25 | Host.CreateDefaultBuilder(args) 26 | //see https://docs.microsoft.com/en-us/aspnet/core/fundamentals/logging/?view=aspnetcore-3.1#add-providers 27 | .ConfigureLogging(logging => 28 | { 29 | logging.ClearProviders(); //Clear logging providers to improve performance 30 | }) 31 | .ConfigureServices(services => 32 | { 33 | services.AddHostedService(); 34 | }) 35 | .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); }); 36 | } 37 | } -------------------------------------------------------------------------------- /ServiceLayer/DatabaseServices/SetupHelpers.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System.IO; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | using DataLayer.EfCode; 8 | using ServiceLayer.DatabaseServices.Concrete; 9 | 10 | namespace ServiceLayer.DatabaseServices 11 | { 12 | public static class SetupHelpers 13 | { 14 | private const string SeedDataSearchName = "Apress books*.json"; 15 | public const string SeedFileSubDirectory = "seedData"; 16 | 17 | public static async Task SeedDatabaseIfNoBooksAsync(this EfCoreContext context, string dataDirectory) 18 | { 19 | var numBooks = context.Books.Count(); 20 | if (numBooks == 0) 21 | { 22 | //the database is empty so we fill it from a json file 23 | var books = BookJsonLoader.LoadBooks(Path.Combine(dataDirectory, SeedFileSubDirectory), 24 | SeedDataSearchName).ToList(); 25 | context.Books.AddRange(books); 26 | await context.SaveChangesAsync(); 27 | 28 | //We add this separately so that it has the highest Id. That will make it appear at the top of the default list 29 | context.Books.Add(SpecialBook.CreateSpecialBook()); 30 | await context.SaveChangesAsync(); 31 | numBooks = books.Count + 1; 32 | } 33 | 34 | return numBooks; 35 | } 36 | } 37 | } -------------------------------------------------------------------------------- /BookApp/HelperExtensions/IsLocalExtension.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Net; 6 | using Microsoft.AspNetCore.Http; 7 | 8 | namespace BookApp.HelperExtensions 9 | { 10 | public static class IsLocalExtension 11 | { 12 | private const string NullIpAddress = "::1"; 13 | 14 | public static bool IsLocal(this HttpRequest req) 15 | { 16 | var connection = req.HttpContext.Connection; 17 | if (connection.RemoteIpAddress.IsSet()) 18 | { 19 | //We have a remote address set up 20 | return connection.LocalIpAddress.IsSet() 21 | //Is local is same as remote, then we are local 22 | ? connection.RemoteIpAddress.Equals(connection.LocalIpAddress) 23 | //else we are remote if the remote IP address is not a loopback address 24 | : IPAddress.IsLoopback(connection.RemoteIpAddress); 25 | } 26 | 27 | return true; 28 | } 29 | 30 | public static void ThrowErrorIfNotLocal(this HttpRequest req) 31 | { 32 | if (!req.IsLocal()) 33 | throw new InvalidOperationException("You can only call this command if you are running locally"); 34 | } 35 | 36 | private static bool IsSet(this IPAddress address) 37 | { 38 | return address != null && address.ToString() != NullIpAddress; 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /Test/UnitTests/TestSupportCode/LinqHelpers.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System.Linq; 5 | 6 | namespace Test.UnitTests.TestSupportCode 7 | { 8 | public static class LinqHelpers //#A 9 | { 10 | public static IQueryable MyOrder //#B 11 | (this IQueryable queryable, //#C 12 | bool ascending) //#D 13 | { 14 | return ascending //#E 15 | ? queryable //#F 16 | .OrderBy(num => num) //#F 17 | : queryable //#G 18 | .OrderByDescending(num => num); //#G 19 | } 20 | } 21 | 22 | /******************************************************* 23 | #A An extension method needs to be defined in a static class 24 | #B The static method Order returns an IQueryable, so that other extension methods can 'chain' on 25 | #C The extension method's first parameter is a) of IQueryable, and b) starts with the 'this' keyword 26 | #D I provide a second parameter that allows me to change the order of the sorting 27 | #E I use the boolean parameter ascending to control whether I add the OrderBy or OrderByDescending LINQ operator to the IQueryable result 28 | #F The ascending parameter was true, so I add the OrderBy LINQ operator to the IQueryable input 29 | #G The ascending parameter was false, so I add the OrderByDescending LINQ operator to the IQueryable input 30 | * *****************************************************/ 31 | } -------------------------------------------------------------------------------- /BookApp/Views/Shared/LogModal.cshtml: -------------------------------------------------------------------------------- 1 | @* // sends in the number of logs *@ 2 | 3 | 4 | -------------------------------------------------------------------------------- /MyFirstEfCoreApp/MyLoggerProvider.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using Microsoft.Extensions.Logging; 7 | 8 | namespace MyFirstEfCoreApp 9 | { 10 | public class MyLoggerProvider : ILoggerProvider 11 | { 12 | private readonly List _logs; 13 | 14 | public MyLoggerProvider(List logs) 15 | { 16 | _logs = logs; 17 | } 18 | 19 | public ILogger CreateLogger(string categoryName) 20 | { 21 | return new MyLogger(_logs); 22 | } 23 | 24 | public void Dispose() 25 | { 26 | } 27 | 28 | private class MyLogger : ILogger 29 | { 30 | private readonly List _logs; 31 | 32 | public MyLogger(List logs) 33 | { 34 | _logs = logs; 35 | } 36 | 37 | public bool IsEnabled(LogLevel logLevel) 38 | { 39 | return logLevel >= LogLevel.Information; 40 | } 41 | 42 | public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, 43 | Func formatter) 44 | { 45 | _logs.Add(formatter(state, exception)); 46 | //Console.WriteLine(formatter(state, exception)); 47 | } 48 | 49 | public IDisposable BeginScope(TState state) 50 | { 51 | return null; 52 | } 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /BookApp/wwwroot/lib/jquery/LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright JS Foundation and other contributors, https://js.foundation/ 2 | 3 | This software consists of voluntary contributions made by many 4 | individuals. For exact contribution history, see the revision history 5 | available at https://github.com/jquery/jquery 6 | 7 | The following license applies to all parts of this software except as 8 | documented below: 9 | 10 | ==== 11 | 12 | Permission is hereby granted, free of charge, to any person obtaining 13 | a copy of this software and associated documentation files (the 14 | "Software"), to deal in the Software without restriction, including 15 | without limitation the rights to use, copy, modify, merge, publish, 16 | distribute, sublicense, and/or sell copies of the Software, and to 17 | permit persons to whom the Software is furnished to do so, subject to 18 | the following conditions: 19 | 20 | The above copyright notice and this permission notice shall be 21 | included in all copies or substantial portions of the Software. 22 | 23 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 24 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 25 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND 26 | NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE 27 | LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION 28 | OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION 29 | WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 30 | 31 | ==== 32 | 33 | All files located in the node_modules and external directories are 34 | externally maintained libraries used by this software which have their 35 | own licenses; we recommend you read them, as their terms may differ from 36 | the terms above. 37 | -------------------------------------------------------------------------------- /BizLogic/GenericInterfaces/BizActionErrors.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System.Collections.Generic; 5 | using System.Collections.Immutable; 6 | using System.ComponentModel.DataAnnotations; 7 | using System.Linq; 8 | 9 | namespace BizLogic.GenericInterfaces 10 | { 11 | public abstract class BizActionErrors //#A 12 | { 13 | private readonly List _errors //#B 14 | = new List(); //#B 15 | 16 | public IImmutableList //#C 17 | Errors => _errors.ToImmutableList();//#C 18 | 19 | public bool HasErrors => _errors.Any(); //#D 20 | 21 | protected void AddError(string errorMessage, //#E 22 | params string[] propertyNames) //#E 23 | { 24 | _errors.Add( new ValidationResult //#F 25 | (errorMessage, propertyNames));//#F 26 | } 27 | } 28 | /*********************************************************** 29 | #A This is an abstract class that provides error handling for business logic 30 | #B I hold the list of validation errors privately 31 | #C But I provide a public, immutable list of errors to the public 32 | #D I create a simple bool HasErrors to make checking for errors easier 33 | #E I created this method to allow a simple error message, or an error message with properties linked to it, to be added to the errors list 34 | #F The validation result has an error message and a, possibly empty, list of properties it is linked to 35 | * *********************************************************/ 36 | } -------------------------------------------------------------------------------- /ServiceLayer/DatabaseServices/Concrete/SpecialBook.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using DataLayer.EfClasses; 7 | 8 | namespace ServiceLayer.DatabaseServices.Concrete 9 | { 10 | public static class SpecialBook 11 | { 12 | public static Book CreateSpecialBook() 13 | { 14 | var book4 = new Book 15 | { 16 | Title = "Quantum Networking", 17 | Description = "Entangled quantum networking provides faster-than-light data communications", 18 | PublishedOn = new DateTime(2057, 1, 1), 19 | Price = 220, 20 | Tags = new List{new Tag{ TagId = "Quantum Entanglement" } } 21 | }; 22 | book4.AuthorsLink = new List 23 | {new BookAuthor {Author = new Author {Name = "Future Person"}, Book = book4}}; 24 | book4.Reviews = new List 25 | { 26 | new Review 27 | { 28 | VoterName = "Jon P Smith", NumStars = 5, 29 | Comment = "I look forward to reading this book, if I am still alive!" 30 | }, 31 | new Review 32 | { 33 | VoterName = "Albert Einstein", NumStars = 5, Comment = "I write this book if I was still alive!" 34 | } 35 | }; 36 | book4.Promotion = new PriceOffer {NewPrice = 219, PromotionalText = "Save $1 if you order 40 years ahead!"}; 37 | 38 | return book4; 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /ServiceLayer/BookServices/BookListDto.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System; 5 | 6 | namespace ServiceLayer.BookServices 7 | { 8 | public class BookListDto 9 | { 10 | public int BookId { get; set; } //#A 11 | public string Title { get; set; } 12 | public DateTime PublishedOn { get; set; } //#B 13 | public decimal Price { get; set; } //#C 14 | 15 | public decimal 16 | ActualPrice { get; set; } //#D 17 | 18 | public string 19 | PromotionPromotionalText { get; set; } //#E 20 | 21 | public string AuthorsOrdered { get; set; } //#F 22 | 23 | public int ReviewsCount { get; set; } //#G 24 | 25 | public double? 26 | ReviewsAverageVotes { get; set; } //#H 27 | 28 | public string[] TagStrings { get; set; } //#I 29 | /****************************************************** 30 | #A I need the Primary Key if the customer clicks the entry to buy the book 31 | #B While the publish date isn't shown we will want to sort by it, so we have to include it 32 | #C This is the normal Price 33 | #D This is the selling price - either the normal price, or the promotional.NewPrice if present 34 | #E The promotional text to show if there is a new price 35 | #F An array of the authors' names in the right order 36 | #G The number of people who reviewed the book 37 | #H The average of all the Votes - null if no votes 38 | #I The Tag names (that is the categories) for this book 39 | * ***************************************************/ 40 | } 41 | } -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to learn about possible attributes. 3 | // Hover to view descriptions of existing attributes. 4 | // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": "MyFirstEfCoreApp (console)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | "program": "${workspaceFolder}/MyFirstEfCoreApp/bin/Debug/netcoreapp5.0/MyFirstEfCoreApp.dll", 13 | "args": [], 14 | "cwd": "${workspaceFolder}", 15 | "stopAtEntry": false, 16 | "console": "integratedTerminal" 17 | }, 18 | { 19 | "name": "Book App (web)", 20 | "type": "coreclr", 21 | "request": "launch", 22 | "preLaunchTask": "build", 23 | "program": "${workspaceFolder}/BookApp/bin/Debug/netcoreapp5.0/BookApp.dll", 24 | "args": [], 25 | "cwd": "${workspaceFolder}/BookApp", 26 | "stopAtEntry": false, 27 | "serverReadyAction": { 28 | "action": "openExternally", 29 | "pattern": "^\\s*Now listening on:\\s+(https?://\\S+)" 30 | }, 31 | "env": { 32 | "ASPNETCORE_ENVIRONMENT": "Development", 33 | "ASPNETCORE_URLS": "https://localhost:5001" 34 | }, 35 | "sourceFileMap": { 36 | "/Views": "${workspaceFolder}/Views" 37 | } 38 | }, 39 | { 40 | "name": ".NET Core Attach", 41 | "type": "coreclr", 42 | "request": "attach", 43 | "processId": "${command:pickProcess}" 44 | } 45 | ] 46 | } -------------------------------------------------------------------------------- /Test/UnitTests/TestBizDbAccess/Ch04_PlaceOrderDbAccess.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using BizDbAccess.Orders; 5 | using DataLayer.EfCode; 6 | using Test.TestHelpers; 7 | using TestSupport.EfHelpers; 8 | using Xunit; 9 | using Xunit.Abstractions; 10 | using Xunit.Extensions.AssertExtensions; 11 | 12 | namespace Test.UnitTests.TestBizDbAccess 13 | { 14 | public class Ch04_PlaceOrderDbAccess 15 | { 16 | private readonly ITestOutputHelper _output; 17 | 18 | public Ch04_PlaceOrderDbAccess(ITestOutputHelper output) 19 | { 20 | _output = output; 21 | } 22 | 23 | [Fact] 24 | public void TestCheckoutListTwoBooksSqLite() 25 | { 26 | //SETUP 27 | var showlog = false; 28 | var options = SqliteInMemory.CreateOptionsWithLogging(log => 29 | { 30 | if (showlog) 31 | _output.WriteLine(log.Message); 32 | }); 33 | using (var context = new EfCoreContext(options)) 34 | { 35 | context.Database.EnsureCreated(); 36 | context.SeedDatabaseFourBooks(); 37 | 38 | var dbAccess = new PlaceOrderDbAccess(context); 39 | 40 | //ATTEMPT 41 | showlog = true; 42 | var booksDict = dbAccess.FindBooksByIdsWithPriceOffers(new []{1, 4}); 43 | 44 | //VERIFY 45 | booksDict.Count.ShouldEqual(2); 46 | booksDict[1].Promotion.ShouldBeNull(); 47 | booksDict[4].Promotion.ShouldNotBeNull(); 48 | } 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /MyFirstEfCoreApp/Program.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System; 5 | 6 | namespace MyFirstEfCoreApp 7 | { 8 | public class Program 9 | { 10 | public static void Main(string[] args) 11 | { 12 | Console.WriteLine( 13 | "Commands: l (list), u (change url), r (resetDb) and e (exit) - add -l to first two for logs"); 14 | Console.Write( 15 | "Checking if database exists... "); 16 | Console.WriteLine(Commands.WipeCreateSeed(true) ? "created database and seeded it." : "it exists."); 17 | do 18 | { 19 | Console.Write("> "); 20 | var command = Console.ReadLine(); 21 | switch (command) 22 | { 23 | case "l": 24 | Commands.ListAll(); 25 | break; 26 | case "u": 27 | Commands.ChangeWebUrl(); 28 | break; 29 | case "l -l": 30 | Commands.ListAllWithLogs(); 31 | break; 32 | case "u -l": 33 | Commands.ChangeWebUrlWithLogs(); 34 | break; 35 | case "r": 36 | Commands.WipeCreateSeed(false); 37 | break; 38 | case "e": 39 | return; 40 | default: 41 | Console.WriteLine("Unknown command."); 42 | break; 43 | } 44 | } while (true); 45 | } 46 | } 47 | } -------------------------------------------------------------------------------- /Test/Mocks/FakeRequestCookieCollection.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System.Collections; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using Microsoft.AspNetCore.Http; 8 | 9 | namespace Test.Mocks 10 | { 11 | public class FakeRequestCookieCollection : IRequestCookieCollection 12 | { 13 | private List> _cookieData = new List>(); 14 | 15 | public FakeRequestCookieCollection(string cookieName = null, string cookieContent = null) 16 | { 17 | 18 | if (cookieName != null) 19 | _cookieData.Add(new KeyValuePair(cookieName, cookieContent)); 20 | } 21 | 22 | public IEnumerator> GetEnumerator() 23 | { 24 | return _cookieData.GetEnumerator(); 25 | } 26 | 27 | IEnumerator IEnumerable.GetEnumerator() 28 | { 29 | return GetEnumerator(); 30 | } 31 | 32 | public bool ContainsKey(string key) 33 | { 34 | return _cookieData.Any(x => x.Key == key); 35 | } 36 | 37 | public bool TryGetValue(string key, out string value) 38 | { 39 | var found = _cookieData.SingleOrDefault(x => x.Key == key); 40 | value = found.Key == key ? found.Value : null; 41 | return found.Key == key; 42 | } 43 | 44 | public int Count => _cookieData.Count(); 45 | 46 | public string this[string key] => _cookieData.SingleOrDefault(x => x.Key == key).Value; 47 | 48 | public ICollection Keys => _cookieData.Select(x => x.Key).ToList(); 49 | } 50 | } -------------------------------------------------------------------------------- /Test/Chapter05Listings/ExampleProgram.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System.Threading.Tasks; 5 | using BookApp; 6 | using Microsoft.AspNetCore.Hosting; 7 | using Microsoft.Extensions.Hosting; 8 | using Microsoft.Extensions.Logging; 9 | 10 | namespace Test.Chapter05Listings 11 | { 12 | public class ExampleProgram 13 | { 14 | public static async Task Main(string[] args) //#A 15 | { 16 | var host = CreateHostBuilder(args).Build(); //#B 17 | await host.MigrateDatabaseAsync(); //#C 18 | await host.RunAsync(); //#D 19 | } 20 | 21 | public static IHostBuilder CreateHostBuilder(string[] args) => 22 | Host.CreateDefaultBuilder(args) 23 | //see https://docs.microsoft.com/en-us/aspnet/core/fundamentals/logging/?tabs=aspnetcore2x&view=aspnetcore-3.0#how-to-add-providers 24 | .ConfigureLogging(logging => 25 | { 26 | logging.ClearProviders(); //Clear logging providers to improve performance 27 | }) 28 | .ConfigureWebHostDefaults(webBuilder => { webBuilder.UseStartup(); }); 29 | } 30 | /********************************************************** 31 | #A You change the Main method to being async so that you can use async/await commands in your SetupDatabaseAsync method 32 | #B This call will run the Startup.Configure method, which sets up the DI services you need to setup/migrate your database 33 | #C this is where you call your extension method to migrate your database 34 | #D At the end you start the ASP.NET Core application. 35 | * ********************************************************/ 36 | } 37 | -------------------------------------------------------------------------------- /Test/Mocks/MockBizActionWithWrite.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using BizLogic.GenericInterfaces; 8 | using DataLayer.EfClasses; 9 | using DataLayer.EfCode; 10 | 11 | namespace Test.Mocks 12 | { 13 | public interface IMockBizActionWithWrite : IBizAction {} 14 | 15 | public enum MockBizActionWithWriteModes { Ok, BizError, SaveChangesError} 16 | public class MockBizActionWithWrite : BizActionErrors, IMockBizActionWithWrite 17 | { 18 | private readonly EfCoreContext _context; 19 | private readonly Guid _userId; 20 | 21 | public MockBizActionWithWrite(EfCoreContext context, Guid userId) 22 | { 23 | _context = context; 24 | _userId = userId; 25 | } 26 | 27 | public string Action(MockBizActionWithWriteModes mode) 28 | { 29 | if (mode == MockBizActionWithWriteModes.BizError) 30 | AddError("There is a biz error."); 31 | 32 | var numBooks = (short) (mode == MockBizActionWithWriteModes.SaveChangesError ? 200 : 1); 33 | 34 | var order = new Order 35 | { 36 | CustomerId = _userId, 37 | LineItems = new List 38 | { 39 | new LineItem 40 | { 41 | BookId = _context.Books.First().BookId, 42 | LineNum = 1, 43 | BookPrice = 123, 44 | NumBooks = numBooks 45 | } 46 | } 47 | }; 48 | 49 | _context.Orders.Add(order); 50 | 51 | return mode.ToString(); 52 | 53 | } 54 | } 55 | } -------------------------------------------------------------------------------- /ServiceLayer/BizRunners/RunnerWriteDbWithValidationAsync.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System.Collections.Immutable; 5 | using System.ComponentModel.DataAnnotations; 6 | using System.Linq; 7 | using System.Threading.Tasks; 8 | using BizLogic.GenericInterfaces; 9 | using DataLayer.EfCode; 10 | 11 | namespace ServiceLayer.BizRunners 12 | { 13 | public class RunnerWriteDbWithValidationAsync //#A 14 | { 15 | private readonly IBizActionAsync _actionClass; //#B 16 | private readonly EfCoreContext _context; 17 | 18 | public RunnerWriteDbWithValidationAsync( 19 | IBizActionAsync actionClass, //#C 20 | EfCoreContext context) 21 | { 22 | _context = context; 23 | _actionClass = actionClass; 24 | } 25 | 26 | public IImmutableList 27 | Errors { get; private set; } 28 | 29 | public bool HasErrors => Errors.Any(); 30 | 31 | public async Task //#D 32 | RunActionAsync(TIn dataIn) //#E 33 | { 34 | var result = await //#F 35 | _actionClass.ActionAsync(dataIn) //#G 36 | .ConfigureAwait(false); //#H 37 | Errors = _actionClass.Errors; 38 | if (!HasErrors) 39 | { 40 | Errors = 41 | (await _context //#I 42 | .SaveChangesWithValidationAsync() //#J 43 | .ConfigureAwait(false)) //#K 44 | .ToImmutableList(); 45 | } 46 | return result; 47 | } 48 | } 49 | /********************************************************* 50 | * 51 | * ******************************************************/ 52 | } -------------------------------------------------------------------------------- /Test/Mocks/MockBizActionWithWriteAsync.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Threading.Tasks; 8 | using BizLogic.GenericInterfaces; 9 | using DataLayer.EfClasses; 10 | using DataLayer.EfCode; 11 | 12 | namespace Test.Mocks 13 | { 14 | public interface IMockBizActionWithWriteAsync : IBizActionAsync {} 15 | 16 | 17 | public class MockBizActionWithWriteAsync : BizActionErrors, IMockBizActionWithWriteAsync 18 | { 19 | private readonly EfCoreContext _context; 20 | private readonly Guid _userId; 21 | 22 | public MockBizActionWithWriteAsync(EfCoreContext context, Guid userId) 23 | { 24 | _context = context; 25 | _userId = userId; 26 | } 27 | 28 | public Task ActionAsync(MockBizActionWithWriteModes mode) 29 | { 30 | if (mode == MockBizActionWithWriteModes.BizError) 31 | AddError("There is a biz error."); 32 | 33 | var numBooks = (short) (mode == MockBizActionWithWriteModes.SaveChangesError ? 200 : 1); 34 | 35 | var order = new Order 36 | { 37 | CustomerId = _userId, 38 | LineItems = new List 39 | { 40 | new LineItem 41 | { 42 | BookId = _context.Books.First().BookId, 43 | LineNum = 1, 44 | BookPrice = 123, 45 | NumBooks = numBooks 46 | } 47 | } 48 | }; 49 | 50 | _context.Orders.Add(order); 51 | 52 | return Task.FromResult( mode.ToString()); 53 | 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /Test/Chapter02Listings/BookLazy1.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using DataLayer.EfClasses; 7 | using Microsoft.EntityFrameworkCore.Infrastructure; 8 | 9 | namespace Test.Chapter02Listings 10 | { 11 | public class BookLazy1 12 | { 13 | public BookLazy1() { } //#A 14 | 15 | private BookLazy1(ILazyLoader lazyLoader) //#B 16 | { //#B 17 | _lazyLoader = lazyLoader; //#B 18 | } //#B 19 | private readonly ILazyLoader _lazyLoader; //#B 20 | 21 | public int Id { get; set; } 22 | 23 | public PriceOffer Promotion { get; set; } //#C 24 | 25 | private ICollection _reviews; //#D 26 | public ICollection Reviews //#E 27 | { 28 | get => _lazyLoader.Load(this, ref _reviews);//#F 29 | set => _reviews = value; //#G 30 | } 31 | } 32 | /****************************************************************** 33 | #A You need a public constructor so that you can create this book in your code 34 | #B This private constructor is used by EF Core to inject the LazyLoader 35 | #C This is a normal relational link which isn't loaded via lazy loading 36 | #D The actual reviews are held in a backing field (see section 8.7) 37 | #E This is the list that you the developer will access 38 | #F A read of the property will trigger a lazy loading of the data (if not already loaded) 39 | #G The set simply updates the backing field 40 | ***************************************************************/ 41 | } -------------------------------------------------------------------------------- /DataLayer/EfClasses/Book.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.ComponentModel.DataAnnotations; 7 | 8 | namespace DataLayer.EfClasses 9 | { 10 | public class Book //#A 11 | { 12 | public int BookId { get; set; } //#B 13 | public string Title { get; set; } 14 | public string Description { get; set; } 15 | public DateTime PublishedOn { get; set; } 16 | public string Publisher { get; set; } 17 | public decimal Price { get; set; } 18 | public string ImageUrl { get; set; } 19 | 20 | public bool SoftDeleted { get; set; } 21 | 22 | //----------------------------------------------- 23 | //relationships 24 | 25 | public PriceOffer Promotion { get; set; } //#C 26 | public ICollection Reviews { get; set; } //#D 27 | 28 | public ICollection Tags { get; set; } //#E 29 | 30 | public ICollection 31 | AuthorsLink { get; set; } //#F 32 | } 33 | 34 | /****************************************************# 35 | #A The Book class contains the main book information 36 | #B I use EF Core's 'by convention' approach to defining the primary key of this entity class. In this case I use Id, and because the property if of type int EF Core assumes that the database will use the SQL IDENTITY command to create a unique key when a new row is added 37 | #C This is the link to the optional PriceOffer 38 | #D There can be zero to many Reviews of the book 39 | #E This is an EF Core 5's automatic many-to-many relationship to the Tag entity class 40 | #F This provides a link to the Many-to-Many linking table that links to the Authors of this book 41 | * **************************************************/ 42 | } -------------------------------------------------------------------------------- /Test/UnitTests/TestServiceLayer/Ch02_Sort.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Linq; 6 | using DataLayer.EfCode; 7 | using ServiceLayer.BookServices.QueryObjects; 8 | using Test.TestHelpers; 9 | using TestSupport.EfHelpers; 10 | using Xunit; 11 | using Xunit.Extensions.AssertExtensions; 12 | 13 | namespace Test.UnitTests.TestServiceLayer 14 | { 15 | public class Ch02_Sort 16 | { 17 | [Fact] 18 | public void CheckSortVotes() 19 | { 20 | //SETUP 21 | var options = SqliteInMemory.CreateOptions(); 22 | using (var context = new EfCoreContext(options)) 23 | { 24 | context.Database.EnsureCreated(); 25 | context.SeedDatabaseFourBooks(); 26 | 27 | //ATTEMPT 28 | var sorted = context.Books.MapBookToDto().OrderBooksBy(OrderByOptions.ByVotes).ToList(); 29 | 30 | //VERIFY 31 | sorted.First().Title.ShouldEqual("Quantum Networking"); 32 | } 33 | } 34 | 35 | [Fact] 36 | public void CheckSortPriceBad() 37 | { 38 | //SETUP 39 | var options = SqliteInMemory.CreateOptions(); 40 | using (var context = new EfCoreContext(options)) 41 | { 42 | context.Database.EnsureCreated(); 43 | context.SeedDatabaseFourBooks(); 44 | 45 | //ATTEMPT 46 | var ex = Assert.Throws(() => 47 | context.Books.MapBookToDto().OrderBooksBy(OrderByOptions.ByPriceLowestFirst).ToList()); 48 | 49 | //VERIFY 50 | ex.Message.ShouldStartWith("SQLite cannot order by expressions of type 'decimal'."); 51 | } 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /Test/UnitTests/TestSupportCode/Ch04_MockHttpCookieAccess.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT licence. See License.txt in the project root for license information. 3 | 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using Test.Mocks; 7 | using Xunit; 8 | using Xunit.Extensions.AssertExtensions; 9 | 10 | namespace Test.UnitTests.TestSupportCode 11 | { 12 | public class Ch04_MockHttpCookieAccess 13 | { 14 | [Fact] 15 | public void CreateEmptyCookiesAccess() 16 | { 17 | //SETUP 18 | 19 | //ATTEMPT 20 | var mockCookieRequests = new MockHttpCookieAccess(); 21 | 22 | //VERIFY 23 | mockCookieRequests.CookiesIn.ShouldNotBeNull(); 24 | mockCookieRequests.CookiesIn.Count.ShouldEqual(0); 25 | mockCookieRequests.CookiesOut.ShouldNotBeNull(); 26 | } 27 | 28 | [Fact] 29 | public void CreateWithExistingInCookie() 30 | { 31 | //SETUP 32 | 33 | //ATTEMPT 34 | var mockCookieRequests = new MockHttpCookieAccess("Test", "Content"); 35 | 36 | //VERIFY 37 | mockCookieRequests.CookiesIn.ShouldNotBeNull(); 38 | mockCookieRequests.CookiesIn.Count.ShouldEqual(1); 39 | mockCookieRequests.CookiesIn["Test"].ShouldEqual("Content"); 40 | } 41 | 42 | [Fact] 43 | public void AddOutCookie() 44 | { 45 | //SETUP 46 | var mockCookieRequests = new MockHttpCookieAccess(); 47 | 48 | //ATTEMPT 49 | mockCookieRequests.CookiesOut.Append("Test", "Content"); 50 | 51 | //VERIFY 52 | mockCookieRequests.ResponseCookieValues.Count.ShouldEqual(1); 53 | mockCookieRequests.ResponseCookieValues.First().ShouldEqual( 54 | new KeyValuePair("Test", "Content")); 55 | } 56 | } 57 | } -------------------------------------------------------------------------------- /Test/Chapter06Listings/EmployeeExtensions.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System; 5 | 6 | namespace Test.Chapter06Listings 7 | { 8 | public static class EmployeeExtensions 9 | { 10 | public static void AddTestEmployeesToDb(this Chapter06Context context) 11 | { 12 | var ceo = new Employee("CEO", Roles.Management, null); 13 | //development 14 | var cto = new Employee("CTO", Roles.Management | Roles.Development, ceo); 15 | var pm1 = new Employee("ProjectManager1", Roles.Management | Roles.Development, cto); 16 | var dev1a = new Employee("dev1a", Roles.Development, pm1); 17 | var dev1b = new Employee("dev1b", Roles.Development, pm1); 18 | var pm2 = new Employee("ProjectManager2", Roles.Management | Roles.Development, cto); 19 | var dev2a = new Employee("dev2a", Roles.Development, pm2); 20 | var dev2b = new Employee("dev2b", Roles.Development, pm2); 21 | //sales 22 | var salesDir = new Employee("SalesDir", Roles.Management | Roles.Sales, ceo); 23 | var sales1 = new Employee("sales1", Roles.Sales, salesDir); 24 | var sales2 = new Employee("sales2", Roles.Sales, salesDir); 25 | 26 | context.AddRange(ceo, cto,pm1, pm2, dev1a, dev1b, dev2a, dev2b, salesDir, sales1, sales2); 27 | context.SaveChanges(); 28 | } 29 | 30 | public static void ShowHierarchical(this Employee employee, Action output, int indent = 0) 31 | { 32 | const int indentSize = 2; 33 | output(new string(' ', indent * indentSize) + employee.Name); 34 | foreach (var person in employee.WorksForMe) 35 | { 36 | person.ShowHierarchical(output, indent + 1); 37 | } 38 | } 39 | } 40 | } -------------------------------------------------------------------------------- /BookApp/Views/Home/About.cshtml: -------------------------------------------------------------------------------- 1 | @{ 2 | ViewData["Title"] = "View"; 3 | } 4 | 5 |

About - Part1

6 | 7 |
8 |
9 |
10 |

11 | Entity Framework Core in Action, second edition 12 |

13 |

14 | Welcome to the example book selling site known as the Book App which is used to show some of the code from the book 15 | Entity Framework Core in Action. 16 |

17 |

What am I seeing?

18 |

19 | This application uses the information found in the chapters two to five of the book to create the Book App. That is: 20 |

    21 |
  • Chapter 2: How to use Query Objects to read the database - see book display with its sorting, filtering, and paging.
  • 22 |
  • Chapter 3: How to update the database - see the Admin dropdowns on each book.
  • 23 |
  • Chapter 4: How to write business logic - try buying a book.
  • 24 |
  • Chapter 5: How to setup the database and call services.
  • 25 |
26 |

27 |
28 |
29 |
30 | 31 | Entity Framework Core in Action 33 | 34 |
35 |
36 |
37 |
-------------------------------------------------------------------------------- /ServiceLayer/BookServices/Concrete/ListBooksService.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System.Linq; 5 | using System.Threading.Tasks; 6 | using DataLayer.EfCode; 7 | using DataLayer.QueryObjects; 8 | using Microsoft.EntityFrameworkCore; 9 | using ServiceLayer.BookServices.QueryObjects; 10 | 11 | namespace ServiceLayer.BookServices.Concrete 12 | { 13 | public class ListBooksService 14 | { 15 | private readonly EfCoreContext _context; 16 | 17 | public ListBooksService(EfCoreContext context) 18 | { 19 | _context = context; 20 | } 21 | 22 | public IQueryable SortFilterPage 23 | (SortFilterPageOptions options) 24 | { 25 | var booksQuery = _context.Books //#A 26 | .AsNoTracking() //#B 27 | .MapBookToDto() //#C 28 | .OrderBooksBy(options.OrderByOptions) //#D 29 | .FilterBooksBy(options.FilterBy, //#E 30 | options.FilterValue); //#E 31 | 32 | options.SetupRestOfDto(booksQuery); //#F 33 | 34 | return booksQuery.Page(options.PageNum - 1, //#G 35 | options.PageSize); //#G 36 | } 37 | } 38 | 39 | /********************************************************* 40 | #A This starts by selecting the Books property in the Application's DbContext 41 | #B Because this is a read-only query I add .AsNoTracking(). It makes the query faster 42 | #C It then uses the Select query object which will pick out/calculate the data it needs 43 | #D It then adds the commands to order the data using the given options 44 | #E Then it adds the commands to filter the data 45 | #F This stage sets up the number of pages and also makes sure PageNum is in the right range 46 | #G Finally it applies the paging commands 47 | * *****************************************************/ 48 | } -------------------------------------------------------------------------------- /Test/Test.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp5.0 5 | 6 | false 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | all 18 | runtime; build; native; contentfiles; analyzers; buildtransitive 19 | 20 | 21 | 22 | 23 | 24 | all 25 | runtime; build; native; contentfiles; analyzers; buildtransitive 26 | 27 | 28 | all 29 | runtime; build; native; contentfiles; analyzers; buildtransitive 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /Test/UnitTests/TestSupportCode/Ch04_MockPlaceOrderDbAccess.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2017 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT licence. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Linq; 6 | using Test.Mocks; 7 | using Test.TestHelpers; 8 | using Xunit; 9 | using Xunit.Extensions.AssertExtensions; 10 | 11 | namespace Test.UnitTests.TestSupportCode 12 | { 13 | public class Ch04_MockPlaceOrderDbAccess 14 | { 15 | [Fact] 16 | public void TestMockDefaultBooks() 17 | { 18 | //SETUP 19 | 20 | //ATTEMPT 21 | var mock = new StubPlaceOrderDbAccess(); 22 | 23 | //VERIFY 24 | mock.Books.Count.ShouldEqual(10); 25 | mock.Books.ForEach(b => b.Promotion.ShouldBeNull()); 26 | mock.Books.ForEach(b => b.PublishedOn.Year.ShouldEqual(EfTestData.DummyBookStartDate.Year)); 27 | } 28 | 29 | [Fact] 30 | public void TestMockIncreasingYearBooks() 31 | { 32 | //SETUP 33 | 34 | //ATTEMPT 35 | var mock = new StubPlaceOrderDbAccess(true); 36 | 37 | //VERIFY 38 | var expectedYear = EfTestData.DummyBookStartDate.Year; 39 | foreach (var book in mock.Books) 40 | { 41 | book.PublishedOn.Year.ShouldEqual(expectedYear++); 42 | } 43 | mock.Books.Last().PublishedOn.Year.ShouldEqual(DateTime.Today.Year+1); 44 | } 45 | 46 | [Fact] 47 | public void TestMockPromotionOnFirstBooks() 48 | { 49 | //SETUP 50 | 51 | //ATTEMPT 52 | var mock = new StubPlaceOrderDbAccess(false,100); 53 | 54 | //VERIFY 55 | mock.Books.Count.ShouldEqual(10); 56 | mock.Books.First().Promotion.ShouldNotBeNull(); 57 | mock.Books.First().Promotion.NewPrice.ShouldEqual(100); 58 | mock.Books.Skip(1).ToList().ForEach(b => b.Promotion.ShouldBeNull()); 59 | } 60 | } 61 | } -------------------------------------------------------------------------------- /BookApp/BookApp.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp5.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | <_ContentIncludedByDefault Remove="wwwroot\js\bundle.min.js" /> 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | all 37 | runtime; build; native; contentfiles; analyzers; buildtransitive 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | -------------------------------------------------------------------------------- /Test/UnitTests/TestDataLayer/Ch06_ComplexQueryOperators.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Linq; 6 | using DataLayer.EfCode; 7 | using Microsoft.EntityFrameworkCore; 8 | using Test.TestHelpers; 9 | using TestSupport.EfHelpers; 10 | using Xunit; 11 | using Xunit.Extensions.AssertExtensions; 12 | 13 | namespace Test.UnitTests.TestDataLayer 14 | { 15 | public class Ch06_ComplexQueryOperators 16 | { 17 | [Fact] 18 | public void TestLinqAggregatesOk() 19 | { 20 | //SETUP 21 | var options = SqliteInMemory.CreateOptions(); 22 | using (var context = new EfCoreContext(options)) 23 | { 24 | context.Database.EnsureCreated(); 25 | context.SeedDatabaseFourBooks(); 26 | 27 | //ATTEMPT 28 | var max = context.Books.Max(x => (double?)x.Price); 29 | var min = context.Books.Min(x => (double?)x.Price); 30 | var sum = context.Books.Sum(x => (double?)x.Price); 31 | var avg = context.Books.Average(x => (double?)x.Price); 32 | var countAverage = context.Books.Count(); 33 | 34 | //VERIFY 35 | } 36 | } 37 | 38 | [Fact] 39 | public void TestLinqAggregateOk() 40 | { 41 | //SETUP 42 | var options = SqliteInMemory.CreateOptions(); 43 | using (var context = new EfCoreContext(options)) 44 | { 45 | context.Database.EnsureCreated(); 46 | context.SeedDatabaseFourBooks(); 47 | 48 | //ATTEMPT 49 | var ex = Assert.Throws(() => 50 | context.Books.Aggregate(0.0, (i, book) => (double)book.Price)); 51 | 52 | //VERIFY 53 | ex.Message.ShouldContain("could not be translated"); 54 | } 55 | } 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /BookApp/HelperExtensions/DatabaseStartupHelpers.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | using DataLayer.EfCode; 8 | using Microsoft.AspNetCore.Hosting; 9 | using Microsoft.EntityFrameworkCore; 10 | using Microsoft.Extensions.DependencyInjection; 11 | using Microsoft.Extensions.Hosting; 12 | using Microsoft.Extensions.Logging; 13 | using ServiceLayer.DatabaseServices; 14 | using ServiceLayer.DatabaseServices.Concrete; 15 | 16 | namespace BookApp.HelperExtensions 17 | { 18 | public static class DatabaseStartupHelpers 19 | { 20 | /// 21 | /// This makes sure the database is created/updated 22 | /// 23 | /// 24 | /// 25 | public static async Task SetupDatabaseAsync(this IHost webHost) 26 | { 27 | using (var scope = webHost.Services.CreateScope()) 28 | { 29 | var services = scope.ServiceProvider; 30 | var env = services.GetRequiredService(); 31 | var context = services.GetRequiredService(); 32 | try 33 | { 34 | var arePendingMigrations = context.Database.GetPendingMigrations().Any(); 35 | await context.Database.MigrateAsync(); 36 | if (arePendingMigrations) 37 | { 38 | await context.SeedDatabaseIfNoBooksAsync(env.WebRootPath); 39 | } 40 | } 41 | catch (Exception ex) 42 | { 43 | var logger = services.GetRequiredService>(); 44 | logger.LogError(ex, "An error occurred while creating/migrating or seeding the database."); 45 | 46 | throw; 47 | } 48 | } 49 | 50 | return webHost; 51 | } 52 | 53 | } 54 | } -------------------------------------------------------------------------------- /ServiceLayer/BizRunners/RunnerWriteDb.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System.Collections.Immutable; 5 | using System.ComponentModel.DataAnnotations; 6 | using BizLogic.GenericInterfaces; 7 | using DataLayer.EfCode; 8 | 9 | namespace ServiceLayer.BizRunners 10 | { 11 | public class RunnerWriteDb 12 | { 13 | private readonly IBizAction _actionClass; 14 | private readonly EfCoreContext _context; 15 | 16 | public RunnerWriteDb( //#B 17 | IBizAction actionClass,//#B 18 | EfCoreContext context) //#B 19 | { 20 | _context = context; 21 | _actionClass = actionClass; 22 | } 23 | 24 | public IImmutableList //#A 25 | Errors => _actionClass.Errors; //#A 26 | 27 | public bool HasErrors => _actionClass.HasErrors;//#A 28 | 29 | public TOut RunAction(TIn dataIn) //#C 30 | { 31 | var result = _actionClass.Action(dataIn); //#D 32 | if (!HasErrors) //#E 33 | _context.SaveChanges();//#E 34 | return result; //#F 35 | } 36 | } 37 | //0123456789|123456789|123456789|123456789|123456789|123456789|123456789|xxxxx! 38 | /********************************************************* 39 | #A The error information from the business logic is passed back to the user of the BizRunner 40 | #B This BizRunner handles business logic that conforms to the IBizAction interface 41 | #C I call RunAction in my Service Layer, or in my Presentation Layer if the data comes back in the right form 42 | #D It runs the business logic I gave it 43 | #E If there aren't any errors it calls SaveChanges to execute any add, update or delete methods 44 | #F Finally it returns the result that the business logic returned 45 | * ******************************************************/ 46 | } -------------------------------------------------------------------------------- /ServiceLayer/CheckoutServices/Concrete/CheckoutListService.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System.Collections.Generic; 5 | using System.Collections.Immutable; 6 | using System.Linq; 7 | using BizLogic.BasketServices; 8 | using BizLogic.Orders; 9 | using DataLayer.EfCode; 10 | using Microsoft.AspNetCore.Http; 11 | 12 | namespace ServiceLayer.CheckoutServices.Concrete 13 | { 14 | public class CheckoutListService 15 | { 16 | private readonly EfCoreContext _context; 17 | private readonly IRequestCookieCollection _cookiesIn; 18 | 19 | public CheckoutListService(EfCoreContext context, IRequestCookieCollection cookiesIn) 20 | { 21 | _context = context; 22 | _cookiesIn = cookiesIn; 23 | } 24 | 25 | public ImmutableList GetCheckoutList() 26 | { 27 | var cookieHandler = new BasketCookie(_cookiesIn); 28 | var service = new CheckoutCookieService(cookieHandler.GetValue()); 29 | 30 | return GetCheckoutList(service.LineItems); 31 | } 32 | 33 | public ImmutableList GetCheckoutList(IImmutableList lineItems) 34 | { 35 | var result = new List(); 36 | foreach (var lineItem in lineItems) 37 | { 38 | result.Add(_context.Books.Select(book => new CheckoutItemDto 39 | { 40 | BookId = book.BookId, 41 | Title = book.Title, 42 | AuthorsName = string.Join(", ", 43 | book.AuthorsLink 44 | .OrderBy(q => q.Order) 45 | .Select(q => q.Author.Name)), 46 | BookPrice = book.Promotion == null ? book.Price : book.Promotion.NewPrice, 47 | ImageUrl = book.ImageUrl, 48 | NumBooks = lineItem.NumBooks 49 | }).Single(y => y.BookId == lineItem.BookId)); 50 | } 51 | return result.ToImmutableList(); 52 | } 53 | } 54 | } -------------------------------------------------------------------------------- /Test/UnitTests/TestServiceLayer/Ch04_RunnerWriteDbWithValidation.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Linq; 6 | using DataLayer.EfCode; 7 | using ServiceLayer.BizRunners; 8 | using Test.Mocks; 9 | using Test.TestHelpers; 10 | using TestSupport.EfHelpers; 11 | using Xunit; 12 | using Xunit.Extensions.AssertExtensions; 13 | 14 | namespace Test.UnitTests.TestServiceLayer 15 | { 16 | public class Ch04_RunnerWriteDbWithValidation 17 | { 18 | [Theory] 19 | [InlineData(MockBizActionWithWriteModes.Ok)] 20 | [InlineData(MockBizActionWithWriteModes.BizError)] 21 | [InlineData(MockBizActionWithWriteModes.SaveChangesError)] 22 | public void RunAction(MockBizActionWithWriteModes mode) 23 | { 24 | //SETUP 25 | var userId = Guid.NewGuid(); 26 | var options = SqliteInMemory.CreateOptions(); 27 | using (var context = new EfCoreContext(options, new FakeUserIdService(userId))) 28 | { 29 | context.Database.EnsureCreated(); 30 | context.SeedDatabaseFourBooks(); 31 | 32 | var action = new MockBizActionWithWrite(context, userId); 33 | var runner = new RunnerWriteDbWithValidation(action, context); 34 | 35 | //ATTEMPT 36 | var output = runner.RunAction(mode); 37 | 38 | //VERIFY 39 | output.ShouldEqual(mode.ToString()); 40 | runner.HasErrors.ShouldEqual(mode != MockBizActionWithWriteModes.Ok); 41 | context.Orders.Count().ShouldEqual(mode != MockBizActionWithWriteModes.Ok ? 0 : 1); 42 | 43 | if (mode == MockBizActionWithWriteModes.BizError) 44 | runner.Errors.Single().ErrorMessage.ShouldEqual("There is a biz error."); 45 | if (mode == MockBizActionWithWriteModes.SaveChangesError) 46 | runner.Errors.Single().ErrorMessage.ShouldEqual("If you want to order a 100 or more books please phone us on 01234-5678-90"); 47 | } 48 | } 49 | } 50 | } -------------------------------------------------------------------------------- /Test/UnitTests/TestAspNetCore/TestDatabaseSetupHelpers.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System.IO; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | using DataLayer.EfCode; 8 | using ServiceLayer.DatabaseServices; 9 | using Test.TestHelpers; 10 | using TestSupport.EfHelpers; 11 | using TestSupport.Helpers; 12 | using Xunit; 13 | using Xunit.Extensions.AssertExtensions; 14 | 15 | namespace Test.UnitTests.TestAspNetCore 16 | { 17 | public class TestDatabaseSetupHelpers 18 | { 19 | [Fact] 20 | public async Task TestSeedDatabaseIfNoBooksAsyncEmptyDatabase() 21 | { 22 | //SETUP 23 | var options = this.CreateUniqueClassOptions(); 24 | using (var context = new EfCoreContext(options)) 25 | { 26 | context.Database.EnsureClean(); 27 | 28 | var callingAssemblyPath = TestData.GetCallingAssemblyTopLevelDir(); 29 | var wwwrootDir = Path.GetFullPath(Path.Combine(callingAssemblyPath, "..\\BookApp\\wwwroot")); 30 | 31 | //ATTEMPT 32 | await context.SeedDatabaseIfNoBooksAsync(wwwrootDir); 33 | 34 | //VERIFY 35 | context.Books.Count().ShouldEqual(54); 36 | } 37 | } 38 | 39 | [Fact] 40 | public async Task TestSeedDatabaseIfNoBooksAsyncBooksAlreadyThere() 41 | { 42 | //SETUP 43 | var options = SqliteInMemory.CreateOptions(); 44 | using (var context = new EfCoreContext(options)) 45 | { 46 | context.Database.EnsureCreated(); 47 | context.SeedDatabaseFourBooks(); 48 | 49 | var callingAssemblyPath = TestData.GetCallingAssemblyTopLevelDir(); 50 | var wwwrootDir = Path.GetFullPath(Path.Combine(callingAssemblyPath, "..\\BookApp\\wwwroot")); 51 | 52 | //ATTEMPT 53 | await context.SeedDatabaseIfNoBooksAsync(wwwrootDir); 54 | 55 | //VERIFY 56 | context.Books.Count().ShouldEqual(4); 57 | } 58 | } 59 | 60 | } 61 | } -------------------------------------------------------------------------------- /Test/UnitTests/TestDataLayer/Ch03_SpliteInMemory.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2016 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT licence. See License.txt in the project root for license information. 3 | 4 | using System.Linq; 5 | using DataLayer.EfCode; 6 | using Microsoft.EntityFrameworkCore; 7 | using Test.TestHelpers; 8 | using TestSupport.EfHelpers; 9 | using Xunit; 10 | using Xunit.Abstractions; 11 | using Xunit.Extensions.AssertExtensions; 12 | 13 | namespace Test.UnitTests.TestDataLayer 14 | { 15 | public class Ch03_SpliteInMemory 16 | { 17 | private readonly ITestOutputHelper _output; 18 | 19 | public Ch03_SpliteInMemory(ITestOutputHelper output) 20 | { 21 | _output = output; 22 | } 23 | 24 | [Fact] 25 | public void TestSqliteInMemoryBasicOk() 26 | { 27 | //SETUP 28 | var options = SqliteInMemory.CreateOptions(); 29 | using var context = new EfCoreContext(options); 30 | context.Database.EnsureCreated(); 31 | 32 | var books = EfTestData.CreateFourBooks(); 33 | context.Books.AddRange(books); 34 | context.SaveChanges(); 35 | 36 | //VERIFY 37 | context.Books.Count().ShouldEqual(4); 38 | context.Books.Count(p => p.Title.StartsWith("Quantum")).ShouldEqual(1); 39 | } 40 | 41 | [Fact] 42 | public void TestSqliteInMemoryTwoContextsOk() 43 | { 44 | //SETUP 45 | var options = SqliteInMemory.CreateOptions(); 46 | options.StopNextDispose(); 47 | //ATTEMPT 48 | using (var context = new EfCoreContext(options)) 49 | { 50 | context.Database.EnsureCreated(); 51 | 52 | var books = EfTestData.CreateFourBooks(); 53 | context.Books.AddRange(books); 54 | context.SaveChanges(); 55 | } 56 | using (var context = new EfCoreContext(options)) 57 | { 58 | //VERIFY 59 | context.Books.Count().ShouldEqual(4); 60 | context.Books.Count(p => p.Title.StartsWith("Quantum")).ShouldEqual(1); 61 | } 62 | } 63 | 64 | 65 | } 66 | } -------------------------------------------------------------------------------- /Test/UnitTests/TestAspNetCore/TestBookJsonLoader.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System.Linq; 5 | using ServiceLayer.DatabaseServices.Concrete; 6 | using TestSupport.Helpers; 7 | using Xunit; 8 | using Xunit.Extensions.AssertExtensions; 9 | 10 | namespace Test.UnitTests.TestAspNetCore 11 | { 12 | public class TestBookJsonLoader 13 | { 14 | [Fact] 15 | public void TestBookLoadOk() 16 | { 17 | //SETUP 18 | const string searchFile = "JsonBooks01*.json"; 19 | var testDataDir = TestData.GetTestDataDir(); 20 | 21 | //ATTEMPT 22 | var books = BookJsonLoader.LoadBooks(testDataDir, searchFile); 23 | 24 | //VERIFY 25 | books.Count().ShouldEqual(4); 26 | } 27 | 28 | [Fact] 29 | public void TestBookLoadBuildReviewsOk() 30 | { 31 | //SETUP 32 | const string searchFile = "JsonBooks01*.json"; 33 | var testDataDir = TestData.GetTestDataDir(); 34 | 35 | //ATTEMPT 36 | var books = BookJsonLoader.LoadBooks(testDataDir, searchFile); 37 | 38 | //VERIFY 39 | var expectedAveVotes = new[] {5.0, 3.0, 4.0, 4.5}; 40 | books.Select(x => x.Reviews.Average(y => y.NumStars)).ShouldEqual(expectedAveVotes); 41 | } 42 | 43 | [Fact] 44 | public void TestBookLoadTagsOk() 45 | { 46 | //SETUP 47 | const string searchFile = "JsonBooks01*.json"; 48 | var testDataDir = TestData.GetTestDataDir(); 49 | 50 | //ATTEMPT 51 | var books = BookJsonLoader.LoadBooks(testDataDir, searchFile).ToList(); 52 | 53 | //VERIFY 54 | books[0].Tags.Select(x => x.TagId).ShouldEqual(new[] { "Web" }); 55 | books[1].Tags.Select(x => x.TagId).ShouldEqual(new []{ "Web" }); 56 | books[2].Tags.Select(x => x.TagId).ShouldEqual(new[] { "Android" }); 57 | books[3].Tags.Select(x => x.TagId).ShouldEqual(new[] { "Microsoft .NET", "Web" }); 58 | books.SelectMany(x => x.Tags).Distinct().Count().ShouldEqual(3); 59 | } 60 | 61 | } 62 | } -------------------------------------------------------------------------------- /BookApp/Logger/RequestTransientLogger.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using Microsoft.AspNetCore.Http; 6 | using Microsoft.Extensions.Logging; 7 | using ServiceLayer.Logger; 8 | 9 | namespace BookApp.Logger 10 | { 11 | /// 12 | /// This logger only logs for the current request, i.e. it overwrites the log when a new request starts 13 | /// 14 | public class RequestTransientLogger : ILoggerProvider 15 | { 16 | public const string NonHttpLogsIdentifier = "non-http-logs"; 17 | 18 | private readonly Func _httpAccessor; 19 | 20 | public static LogLevel LogThisAndAbove { get; set; } = LogLevel.Information; 21 | 22 | public RequestTransientLogger(Func httpAccessor) 23 | { 24 | _httpAccessor = httpAccessor; 25 | } 26 | 27 | public ILogger CreateLogger(string categoryName) 28 | { 29 | return new MyLogger(_httpAccessor); 30 | } 31 | 32 | public void Dispose() 33 | { 34 | } 35 | 36 | private class MyLogger : ILogger 37 | { 38 | private readonly Func _httpAccessor; 39 | 40 | public MyLogger(Func httpAccessor) 41 | { 42 | _httpAccessor = httpAccessor; 43 | 44 | } 45 | 46 | public bool IsEnabled(LogLevel logLevel) 47 | { 48 | return logLevel >= LogThisAndAbove; 49 | } 50 | 51 | public void Log(LogLevel logLevel, EventId eventId, TState state, Exception exception, 52 | Func formatter) 53 | { 54 | var currHttpContext = _httpAccessor().HttpContext; 55 | HttpRequestLog.AddLog(currHttpContext?.TraceIdentifier ?? NonHttpLogsIdentifier, 56 | logLevel, eventId, formatter(state, exception)); 57 | } 58 | 59 | public IDisposable BeginScope(TState state) 60 | { 61 | return null; 62 | } 63 | } 64 | } 65 | } -------------------------------------------------------------------------------- /Test/Mocks/StubPlaceOrderDbAccess.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Collections.Immutable; 7 | using System.Linq; 8 | using BizDbAccess.Orders; 9 | using DataLayer.EfClasses; 10 | using Test.TestHelpers; 11 | 12 | namespace Test.Mocks 13 | { 14 | public class StubPlaceOrderDbAccess : IPlaceOrderDbAccess 15 | { 16 | /// 17 | /// 18 | /// 19 | /// If true then the last book will be in the future 20 | /// if number it adds a promotion to the first book 21 | public StubPlaceOrderDbAccess(bool createLastInFuture = false, int? promotionPriceForFirstBook = null) 22 | { 23 | var numBooks = createLastInFuture ? DateTime.UtcNow.Year - EfTestData.DummyBookStartDate.Year + 2 : 10; 24 | var books = EfTestData.CreateDummyBooks(numBooks, createLastInFuture); 25 | if (promotionPriceForFirstBook != null) 26 | books.First().Promotion = new PriceOffer 27 | { 28 | NewPrice = (int)promotionPriceForFirstBook, 29 | PromotionalText = "Unit Test" 30 | }; 31 | Books = books.ToImmutableList(); 32 | } 33 | 34 | public ImmutableList Books { get; private set; } 35 | 36 | public Order AddedOrder { get; private set; } 37 | 38 | 39 | /// 40 | /// This finds any books that fits the BookIds given to it 41 | /// and includes any promoptions 42 | /// 43 | /// 44 | /// A dictionary with the BookId as the key, and the Book as the value 45 | public IDictionary FindBooksByIdsWithPriceOffers(IEnumerable bookIds) 46 | { 47 | return Books.AsQueryable().Where(x => bookIds.Contains(x.BookId)) 48 | .ToDictionary(key => key.BookId); 49 | } 50 | 51 | public void Add(Order newOrder) 52 | { 53 | AddedOrder = newOrder; 54 | } 55 | } 56 | } -------------------------------------------------------------------------------- /ServiceLayer/DataKeyServices/Concrete/UserIdService.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using BizLogic.BasketServices; 6 | using DataLayer.EfCode; 7 | using Microsoft.AspNetCore.Http; 8 | using ServiceLayer.CheckoutServices.Concrete; 9 | 10 | namespace ServiceLayer.DataKeyServices.Concrete 11 | { 12 | public class UserIdService : IUserIdService 13 | { 14 | private readonly IHttpContextAccessor _httpAccessor; //#A 15 | 16 | public UserIdService(IHttpContextAccessor httpAccessor) //#A 17 | { //#A 18 | _httpAccessor = httpAccessor; //#A 19 | } //#A 20 | 21 | public Guid GetUserId() 22 | { 23 | var httpContext = _httpAccessor.HttpContext; //#B 24 | if (httpContext == null) //#B 25 | return Guid.Empty; //#B 26 | 27 | var cookie = new BasketCookie(httpContext.Request.Cookies); //#C 28 | if (!cookie.Exists()) //#C 29 | return Guid.Empty; //#C 30 | 31 | var service = new CheckoutCookieService(cookie.GetValue()); //#D 32 | return service.UserId; //#D 33 | } 34 | } 35 | /****************************************************** 36 | #A The IHttpContextAccessor is a way to access the current HTTP context. To use this you need to register this in Statup class using the command 'services.AddHttpContextAccessor()' 37 | #B There are cases where the HTTPContext could be null, say in a background task. In this case you provide an empty GUID 38 | #C This uses existing services to look for the basket cookie. If there is no cookie then it returns an empty GUID 39 | #D If there is a basket cookie then you create the CheckoutCookieService which extracts the UserId. This UserId it returned 40 | ******************************************************/ 41 | } -------------------------------------------------------------------------------- /Test/UnitTests/TestServiceLayer/Ch04_RunnerWriteDbWithValidationAsync.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | using DataLayer.EfCode; 8 | using ServiceLayer.BizRunners; 9 | using Test.Mocks; 10 | using Test.TestHelpers; 11 | using TestSupport.EfHelpers; 12 | using Xunit; 13 | using Xunit.Extensions.AssertExtensions; 14 | 15 | namespace Test.UnitTests.TestServiceLayer 16 | { 17 | public class Ch04_RunnerWriteDbWithValidationAsync 18 | { 19 | [Theory] 20 | [InlineData(MockBizActionWithWriteModes.Ok)] 21 | [InlineData(MockBizActionWithWriteModes.BizError)] 22 | [InlineData(MockBizActionWithWriteModes.SaveChangesError)] 23 | public async Task RunActionAsync(MockBizActionWithWriteModes mode) 24 | { 25 | //SETUP 26 | var userId = Guid.NewGuid(); 27 | var options = SqliteInMemory.CreateOptions(); 28 | using (var context = new EfCoreContext(options, new FakeUserIdService(userId))) 29 | { 30 | context.Database.EnsureCreated(); 31 | context.SeedDatabaseFourBooks(); 32 | 33 | var action = new MockBizActionWithWriteAsync(context, userId); 34 | var runner = new RunnerWriteDbWithValidationAsync(action, context); 35 | 36 | //ATTEMPT 37 | var output = await runner.RunActionAsync(mode); 38 | 39 | //VERIFY 40 | output.ShouldEqual(mode.ToString()); 41 | runner.HasErrors.ShouldEqual(mode != MockBizActionWithWriteModes.Ok); 42 | context.Orders.Count().ShouldEqual(mode != MockBizActionWithWriteModes.Ok ? 0 : 1); 43 | 44 | if (mode == MockBizActionWithWriteModes.BizError) 45 | runner.Errors.Single().ErrorMessage.ShouldEqual("There is a biz error."); 46 | if (mode == MockBizActionWithWriteModes.SaveChangesError) 47 | runner.Errors.Single().ErrorMessage.ShouldEqual("If you want to order a 100 or more books please phone us on 01234-5678-90"); 48 | } 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /DataLayer/Migrations/20200921133547_AddTags.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.EntityFrameworkCore.Migrations; 2 | 3 | namespace DataLayer.Migrations 4 | { 5 | public partial class AddTags : Migration 6 | { 7 | protected override void Up(MigrationBuilder migrationBuilder) 8 | { 9 | migrationBuilder.CreateTable( 10 | name: "Tags", 11 | columns: table => new 12 | { 13 | TagId = table.Column(type: "nvarchar(40)", maxLength: 40, nullable: false) 14 | }, 15 | constraints: table => 16 | { 17 | table.PrimaryKey("PK_Tags", x => x.TagId); 18 | }); 19 | 20 | migrationBuilder.CreateTable( 21 | name: "BookTag", 22 | columns: table => new 23 | { 24 | BooksBookId = table.Column(type: "int", nullable: false), 25 | TagsTagId = table.Column(type: "nvarchar(40)", nullable: false) 26 | }, 27 | constraints: table => 28 | { 29 | table.PrimaryKey("PK_BookTag", x => new { x.BooksBookId, x.TagsTagId }); 30 | table.ForeignKey( 31 | name: "FK_BookTag_Books_BooksBookId", 32 | column: x => x.BooksBookId, 33 | principalTable: "Books", 34 | principalColumn: "BookId", 35 | onDelete: ReferentialAction.Cascade); 36 | table.ForeignKey( 37 | name: "FK_BookTag_Tags_TagsTagId", 38 | column: x => x.TagsTagId, 39 | principalTable: "Tags", 40 | principalColumn: "TagId", 41 | onDelete: ReferentialAction.Cascade); 42 | }); 43 | 44 | migrationBuilder.CreateIndex( 45 | name: "IX_BookTag_TagsTagId", 46 | table: "BookTag", 47 | column: "TagsTagId"); 48 | } 49 | 50 | protected override void Down(MigrationBuilder migrationBuilder) 51 | { 52 | migrationBuilder.DropTable( 53 | name: "BookTag"); 54 | 55 | migrationBuilder.DropTable( 56 | name: "Tags"); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /ServiceLayer/OrderServices/Concrete/PlaceOrderServiceWithVal.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System.Collections.Immutable; 5 | using System.ComponentModel.DataAnnotations; 6 | using BizDbAccess.Orders; 7 | using BizLogic.BasketServices; 8 | using BizLogic.Orders; 9 | using BizLogic.Orders.Concrete; 10 | using DataLayer.EfClasses; 11 | using DataLayer.EfCode; 12 | using Microsoft.AspNetCore.Http; 13 | using ServiceLayer.BizRunners; 14 | using ServiceLayer.CheckoutServices.Concrete; 15 | 16 | namespace ServiceLayer.OrderServices.Concrete 17 | { 18 | public class PlaceOrderServiceWithVal 19 | { 20 | private readonly BasketCookie _basketCookie; 21 | private readonly RunnerWriteDbWithValidation _runner; 22 | 23 | public PlaceOrderServiceWithVal( 24 | IRequestCookieCollection cookiesIn, 25 | IResponseCookies cookiesOut, 26 | EfCoreContext context) 27 | { 28 | _basketCookie = new BasketCookie(cookiesIn, cookiesOut); 29 | _runner = new RunnerWriteDbWithValidation( 30 | new PlaceOrderAction( 31 | new PlaceOrderDbAccess(context)), 32 | context); 33 | } 34 | 35 | public IImmutableList Errors => _runner.Errors; 36 | 37 | /// 38 | /// This creates the order and, if successful clears the cookie 39 | /// 40 | /// Returns the OrderId, or zero if errors 41 | public int PlaceOrder(bool acceptTAndCs) 42 | { 43 | var checkoutService = new CheckoutCookieService( 44 | _basketCookie.GetValue()); 45 | 46 | var order = _runner.RunAction( 47 | new PlaceOrderInDto(acceptTAndCs, 48 | checkoutService.UserId, checkoutService.LineItems)); 49 | 50 | if (_runner.HasErrors) return 0; 51 | 52 | //successful so clear the cookie line items 53 | checkoutService.ClearAllLineItems(); 54 | _basketCookie.AddOrUpdateCookie( 55 | checkoutService.EncodeForCookie()); 56 | 57 | return order.OrderId; 58 | } 59 | } 60 | } -------------------------------------------------------------------------------- /Test/UnitTests/TestServiceLayer/Ch03_ChangePriceOfferService.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.Linq; 6 | using DataLayer.EfCode; 7 | using ServiceLayer.AdminServices.Concrete; 8 | using ServiceLayer.CheckoutServices.Concrete; 9 | using Test.Mocks; 10 | using Test.TestHelpers; 11 | using TestSupport.EfHelpers; 12 | using Xunit; 13 | using Xunit.Extensions.AssertExtensions; 14 | 15 | namespace Test.UnitTests.TestServiceLayer 16 | { 17 | public class Ch03_ChangePriceOfferService 18 | { 19 | [Theory] 20 | [InlineData(0, false)] 21 | [InlineData(3, true)] 22 | public void TestGetOriginal(int bookIndex, bool hasPromotion) 23 | { 24 | //SETUP 25 | var options = SqliteInMemory.CreateOptions(); 26 | using (var context = new EfCoreContext(options)) 27 | { 28 | context.Database.EnsureCreated(); 29 | var books = context.SeedDatabaseFourBooks(); 30 | 31 | var service = new ChangePriceOfferService(context); 32 | 33 | //ATTEMPT 34 | var priceOffer = service.GetOriginal(books[bookIndex].BookId); 35 | 36 | //VERIFY 37 | (priceOffer.NewPrice != books[bookIndex].Price).ShouldEqual(hasPromotion); 38 | } 39 | } 40 | 41 | [Theory] 42 | [InlineData(0, 1)] 43 | [InlineData(3, 0)] 44 | public void AddRemovePriceOffer(int bookIndex, int numPriceOffers) 45 | { 46 | //SETUP 47 | var options = SqliteInMemory.CreateOptions(); 48 | using (var context = new EfCoreContext(options)) 49 | { 50 | context.Database.EnsureCreated(); 51 | var books = context.SeedDatabaseFourBooks(); 52 | 53 | var service = new ChangePriceOfferService(context); 54 | var priceOffer = service.GetOriginal(books[bookIndex].BookId); 55 | 56 | //ATTEMPT 57 | var error = service.AddRemovePriceOffer(priceOffer); 58 | 59 | //VERIFY 60 | context.PriceOffers.Count().ShouldEqual(numPriceOffers); 61 | } 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /Test/UnitTests/TestSupportCode/Ch02_AppSettings.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using Microsoft.Data.SqlClient; 5 | using Microsoft.Extensions.Configuration; 6 | using TestSupport.Helpers; 7 | using Xunit; 8 | using Xunit.Extensions.AssertExtensions; 9 | 10 | namespace Test.UnitTests.TestSupportCode 11 | { 12 | public class Ch02_AppSettings 13 | { 14 | [Fact] 15 | public void GetConfigurationOk() 16 | { 17 | //SETUP 18 | 19 | //ATTEMPT 20 | var config = AppSettings.GetConfiguration(); 21 | 22 | //VERIFY 23 | config.GetConnectionString("UnitTestConnection") 24 | .ShouldEqual("Server=(localdb)\\mssqllocaldb;Database=EfCoreInActionDb2-Test;Trusted_Connection=True;MultipleActiveResultSets=true"); 25 | } 26 | 27 | [Fact] 28 | public void GetTestConnectionStringOk() 29 | { 30 | //SETUP 31 | var config = AppSettings.GetConfiguration(); 32 | var orgDbName = new SqlConnectionStringBuilder(config.GetConnectionString("UnitTestConnection")) 33 | .InitialCatalog; 34 | 35 | //ATTEMPT 36 | var con = this.GetUniqueDatabaseConnectionString(); 37 | 38 | //VERIFY 39 | var newDatabaseName = new SqlConnectionStringBuilder(con).InitialCatalog; 40 | Assert.StartsWith($"{orgDbName}_", newDatabaseName); 41 | Assert.EndsWith($"{typeof(Ch02_AppSettings).Name}", newDatabaseName); 42 | } 43 | 44 | 45 | [Fact] 46 | public void GetTestConnectionStringWithExtraMethodNameOk() 47 | { 48 | //SETUP 49 | var config = AppSettings.GetConfiguration(); 50 | var orgDbName = new SqlConnectionStringBuilder(config.GetConnectionString("UnitTestConnection")) 51 | .InitialCatalog; 52 | 53 | //ATTEMPT 54 | var con = this.GetUniqueDatabaseConnectionString("ExtraMethodName"); 55 | 56 | //VERIFY 57 | var newDatabaseName = new SqlConnectionStringBuilder(con).InitialCatalog; 58 | Assert.StartsWith($"{orgDbName}_", newDatabaseName); 59 | Assert.EndsWith($"{typeof(Ch02_AppSettings).Name}_ExtraMethodName", newDatabaseName); 60 | } 61 | } 62 | } -------------------------------------------------------------------------------- /Test/UnitTests/TestServiceLayer/Ch02_ListBooksService.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System.Linq; 5 | using DataLayer.EfCode; 6 | using ServiceLayer.BookServices; 7 | using ServiceLayer.BookServices.Concrete; 8 | using ServiceLayer.BookServices.QueryObjects; 9 | using Test.TestHelpers; 10 | using TestSupport.EfHelpers; 11 | using Xunit; 12 | using Xunit.Extensions.AssertExtensions; 13 | 14 | namespace Test.UnitTests.TestServiceLayer 15 | { 16 | public class Ch02_ListBooksService 17 | { 18 | [Theory] 19 | [InlineData(OrderByOptions.SimpleOrder)] 20 | [InlineData(OrderByOptions.ByPublicationDate)] 21 | public void OrderBooksBy(OrderByOptions orderByOptions) 22 | { 23 | //SETUP 24 | var numBooks = 5; 25 | var options = SqliteInMemory.CreateOptions(); 26 | using (var context = new EfCoreContext(options)) 27 | { 28 | context.Database.EnsureCreated(); 29 | context.SeedDatabaseDummyBooks(numBooks); 30 | 31 | //ATTEMPT 32 | var service = new ListBooksService(context); 33 | var listOptions = new SortFilterPageOptions() {OrderByOptions = orderByOptions}; 34 | var dtos = service.SortFilterPage(listOptions).ToList(); 35 | 36 | //VERIFY 37 | dtos.Count.ShouldEqual(numBooks); 38 | } 39 | } 40 | 41 | [Theory] 42 | [InlineData(5)] 43 | [InlineData(10)] 44 | public void PageBooks(int pageSize) 45 | { 46 | //SETUP 47 | var numBooks = 12; 48 | var options = SqliteInMemory.CreateOptions(); 49 | using (var context = new EfCoreContext(options)) 50 | { 51 | context.Database.EnsureCreated(); 52 | context.SeedDatabaseDummyBooks(numBooks); 53 | 54 | //ATTEMPT 55 | var service = new ListBooksService(context); 56 | var listOptions = new SortFilterPageOptions() {PageSize = pageSize}; 57 | var dtos = service.SortFilterPage(listOptions).ToList(); 58 | 59 | //VERIFY 60 | dtos.Count.ShouldEqual(pageSize); 61 | } 62 | } 63 | } 64 | } -------------------------------------------------------------------------------- /ServiceLayer/BizRunners/RunnerWriteDbWithValidation.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System.Collections.Immutable; 5 | using System.ComponentModel.DataAnnotations; 6 | using System.Linq; 7 | using BizLogic.GenericInterfaces; 8 | using DataLayer.EfCode; 9 | 10 | namespace ServiceLayer.BizRunners 11 | { 12 | public class RunnerWriteDbWithValidation 13 | { 14 | private readonly IBizAction _actionClass; 15 | private readonly EfCoreContext _context; 16 | 17 | public RunnerWriteDbWithValidation( //#B 18 | IBizAction actionClass, //#B 19 | EfCoreContext context) //#B 20 | { 21 | _context = context; 22 | _actionClass = actionClass; 23 | } 24 | 25 | public IImmutableList//#A 26 | Errors { get; private set; } //#A 27 | 28 | public bool HasErrors => Errors.Any(); //#A 29 | 30 | public TOut RunAction(TIn dataIn) //#C 31 | { 32 | var result = _actionClass.Action(dataIn); //#D 33 | Errors = _actionClass.Errors; //#E 34 | if (!HasErrors) //#F 35 | { 36 | Errors = //#G 37 | _context.SaveChangesWithValidation()//#G 38 | .ToImmutableList(); //#G 39 | } 40 | return result; //#H 41 | } 42 | } 43 | //0123456789|123456789|123456789|123456789|123456789|123456789|123456789|xxxxx! 44 | /********************************************************* 45 | #A Ths version needs its own Errors/HasErrors properties, as errors come from two sources 46 | #B This handles business logic that conforms to the IBizAction interface 47 | #C This method is called to execute the business logic and handle any errors 48 | #D It runs the business logic I gave it 49 | #E Any errors from the business logic are assigned to the local errors list 50 | #F If there aren't any errors it calls SaveChangesWithChecking 51 | #G Any validation errors are assigned the the Errors list 52 | #H Finally it returns the result that the business logic returned 53 | * ******************************************************/ 54 | } -------------------------------------------------------------------------------- /ServiceLayer/BookServices/QueryObjects/BookListDtoSort.cs: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2020 Jon P Smith, GitHub: JonPSmith, web: http://www.thereformedprogrammer.net/ 2 | // Licensed under MIT license. See License.txt in the project root for license information. 3 | 4 | using System; 5 | using System.ComponentModel.DataAnnotations; 6 | using System.Linq; 7 | 8 | namespace ServiceLayer.BookServices.QueryObjects 9 | { 10 | public enum OrderByOptions 11 | { 12 | [Display(Name = "sort by...")] SimpleOrder = 0, 13 | [Display(Name = "Votes ↑")] ByVotes, 14 | [Display(Name = "Publication Date ↑")] ByPublicationDate, 15 | [Display(Name = "Price ↓")] ByPriceLowestFirst, 16 | [Display(Name = "Price ↑")] ByPriceHigestFirst 17 | } 18 | 19 | public static class BookListDtoSort 20 | { 21 | public static IQueryable OrderBooksBy 22 | (this IQueryable books, 23 | OrderByOptions orderByOptions) 24 | { 25 | switch (orderByOptions) 26 | { 27 | case OrderByOptions.SimpleOrder: //#A 28 | return books.OrderByDescending( //#A 29 | x => x.BookId); //#A 30 | case OrderByOptions.ByVotes: //#B 31 | return books.OrderByDescending(x => //#B 32 | x.ReviewsAverageVotes); //#B 33 | case OrderByOptions.ByPublicationDate: //#C 34 | return books.OrderByDescending( //#C 35 | x => x.PublishedOn); //#C 36 | case OrderByOptions.ByPriceLowestFirst: //#D 37 | return books.OrderBy(x => x.ActualPrice); //#D 38 | case OrderByOptions.ByPriceHigestFirst: //#D 39 | return books.OrderByDescending( //#D 40 | x => x.ActualPrice); //#D 41 | default: 42 | throw new ArgumentOutOfRangeException( 43 | nameof(orderByOptions), orderByOptions, null); 44 | } 45 | } 46 | 47 | /************************************************************ 48 | #A Because of paging we always need to sort. I default to showing latest entries first 49 | #B This orders the book by votes. Books without any votes (null return) go at the bottom 50 | #C Order by publication date - latest books at the top 51 | #D Order by actual price, which takes into account any promotional price - both lowest first and highest first 52 | * ********************************************************/ 53 | } 54 | } -------------------------------------------------------------------------------- /BookApp/wwwroot/js/bundle.min.js: -------------------------------------------------------------------------------- 1 | var BookList=function(n,t){"use strict";function u(t,i){var r=n("#filter-value-group");i?(t.prop("disabled",!1),r.removeClass("dim-filter-value")):(t.prop("disabled",!0),r.addClass("dim-filter-value"))}function i(i,f,e){f=f||"";var o=n("#filter-value-dropdown");u(o,!1);i!=="NoFilter"&&n.ajax({url:r,data:{FilterBy:i}}).done(function(i){e||t.newTrace(i.traceIdentifier,i.numLogs);o.find("option").remove().end().append(n("