├── .gitignore ├── chapter-1-ticket-sales └── src │ ├── .gitignore │ ├── TicketSales.Api │ ├── .vscode │ │ ├── launch.json │ │ └── tasks.json │ ├── Controllers │ │ ├── TicketInventoryController.cs │ │ └── TicketOrderController.cs │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── Startup.cs │ ├── TicketSales.Api.csproj │ ├── appsettings.Development.json │ └── appsettings.json │ ├── TicketSales.Common │ ├── ConsoleHelper.cs │ ├── ConsoleLogger.cs │ ├── ILogger.cs │ ├── Models │ │ ├── DataResult.cs │ │ └── TicketInformation.cs │ └── TicketSales.Common.csproj │ ├── TicketSales.MockClient │ ├── .vscode │ │ ├── launch.json │ │ └── tasks.json │ ├── Program.cs │ ├── TicketInformation.cs │ ├── TicketSales.MockClient.csproj │ └── appsettings.json │ ├── TicketSales.Proxy │ ├── Controllers │ │ └── BookingController.cs │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── Startup.cs │ ├── TicketInformation.cs │ ├── TicketSales.Proxy.csproj │ ├── appsettings.Development.json │ └── appsettings.json │ ├── TicketSales.Service │ ├── Program.cs │ ├── TicketCreationResponse.cs │ ├── TicketInformation.cs │ ├── TicketSales.Service.csproj │ └── appsettings.json │ ├── TicketSales.ServiceBusHelper │ ├── Configuration │ │ └── ServiceBusConfiguration.cs │ ├── IQueueHelper.cs │ ├── QueueHelper.cs │ └── TicketSales.ServiceBusHelper.csproj │ ├── TicketSales.ThirdPartyApi │ ├── .vscode │ │ ├── launch.json │ │ └── tasks.json │ ├── Controllers │ │ └── ExternalTicketBookingController.cs │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── Startup.cs │ ├── TicketInformation.cs │ ├── TicketSales.ThirdPartyApi.csproj │ ├── appsettings.Development.json │ ├── appsettings.json │ └── readme.md │ ├── TicketSales.ThirdPartyProxy │ ├── ITicketService.cs │ ├── TicketSales.ThirdPartyProxy.csproj │ ├── TicketService.cs │ └── TicketServiceConfiguration.cs │ └── TicketSales.sln ├── chapter-2-cash-desk └── src │ └── CashDesk │ ├── CashDesk.Client │ ├── CashDesk.Client.csproj │ └── Program.cs │ ├── CashDesk.DataPersistence │ ├── CashDesk.DataPersistence.csproj │ ├── DataEventsPersistence.cs │ └── IDataEventsPersistence.cs │ ├── CashDesk.Server │ ├── CashDesk.Server.csproj │ ├── CashDeskEventStream.cs │ ├── Entities │ │ ├── CashDeskTransaction.cs │ │ ├── CashDeskTransactionConverter.cs │ │ └── ITransaction.cs │ ├── EventStreamBase.cs │ ├── EventTypes │ │ ├── CashDeskTransactionAddedEvent.cs │ │ └── IEvent.cs │ └── Persistence │ │ ├── DataCashDeskTransactionAddedEvent.cs │ │ ├── IDataEvent.cs │ │ ├── PersistData.cs │ │ └── SaveChanges.cs │ ├── CashDesk.UnitTesks │ ├── CashDesk.Tesks.csproj │ ├── Integration │ │ └── Persistence.cs │ ├── Mocks │ │ └── DataEventsPersistenceMock.cs │ └── Unit │ │ └── EventSourcingTests.cs │ └── CashDesk.sln ├── chapter-3-travel-agent ├── src │ └── TravelAgent │ │ └── TravelAgent │ │ ├── TravelAgent.Client │ │ ├── BookingRequest.cs │ │ ├── Program.cs │ │ ├── TravelAgent.Client.csproj │ │ └── appsettings.json │ │ ├── TravelAgent.Coordinator │ │ ├── ApiProxies │ │ │ ├── HospitalProxy.cs │ │ │ ├── HotelProxy.cs │ │ │ └── SpaceFlightProxy.cs │ │ ├── BookingRequest.cs │ │ ├── BookingRequestHandler.cs │ │ ├── IBookingRequestHandler.cs │ │ ├── Program.cs │ │ ├── TravelAgent.Coordinator.csproj │ │ └── appsettings.json │ │ ├── TravelAgent.HospitalApi │ │ ├── Controllers │ │ │ └── HospitalTestController.cs │ │ ├── Models │ │ │ └── RoomRequest.cs │ │ ├── Program.cs │ │ ├── Properties │ │ │ └── launchSettings.json │ │ ├── Startup.cs │ │ ├── TravelAgent.HospitalApi.csproj │ │ ├── appsettings.Development.json │ │ └── appsettings.json │ │ ├── TravelAgent.HotelApi │ │ ├── Controllers │ │ │ ├── AvailabilityController.cs │ │ │ └── BookingController.cs │ │ ├── Models │ │ │ ├── AvailabilityResponse.cs │ │ │ └── RoomRequest.cs │ │ ├── Program.cs │ │ ├── Properties │ │ │ └── launchSettings.json │ │ ├── Startup.cs │ │ ├── TravelAgent.HotelApi.csproj │ │ ├── appsettings.Development.json │ │ └── appsettings.json │ │ ├── TravelAgent.SpaceTripApi │ │ ├── Controllers │ │ │ └── SpaceFlightBookingController.cs │ │ ├── Models │ │ │ └── BookingRequest.cs │ │ ├── Program.cs │ │ ├── Properties │ │ │ └── launchSettings.json │ │ ├── Startup.cs │ │ ├── TravelAgent.SpaceTripApi.csproj │ │ ├── appsettings.Development.json │ │ └── appsettings.json │ │ └── TravelAgent.sln └── tests │ └── TravelAgent.ApiTests │ ├── Properties │ └── launchSettings.json │ ├── TestHotelApi.cs │ └── TravelAgent.ApiTests.csproj ├── chapter-4-social-media └── src │ └── SocialMedia │ ├── SocialMedia.Client │ ├── Helpers │ │ └── Extensions.cs │ ├── Program.cs │ └── SocialMedia.Client.csproj │ ├── SocialMedia.Data.Mongo │ ├── Entities │ │ ├── Comment.cs │ │ ├── EntityBase.cs │ │ └── Post.cs │ ├── IMongoDBWrapper.cs │ ├── MongoDBWrapper.cs │ └── SocialMedia.Data.Mongo.csproj │ ├── SocialMedia.Transfer │ ├── Program.cs │ └── SocialMedia.ProcessDataService.csproj │ ├── SocialMedia.UpdateService │ ├── Controllers │ │ ├── CommentController.cs │ │ └── PostController.cs │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── SocialMedia.UpdateService.csproj │ ├── Startup.cs │ ├── appsettings.Development.json │ └── appsettings.json │ └── SocialMedia.sln ├── chapter-5-admin ├── extended │ └── CommitCustomerDataExtended │ │ ├── CommitCustomerDataExtended.sln │ │ └── CommitCustomerDataExtended │ │ ├── CommitCustomerData.cs │ │ └── CommitCustomerDataExtended.csproj ├── src │ └── Admin │ │ ├── Admin.App │ │ ├── Admin.App.csproj │ │ └── Program.cs │ │ ├── Admin.Common │ │ ├── Admin.Common.csproj │ │ └── CustomerModel.cs │ │ ├── Admin.CustomerRead │ │ ├── Admin.CustomerRead.csproj │ │ ├── IReadService.cs │ │ └── ReadService.cs │ │ ├── Admin.CustomerUpdate │ │ ├── Admin.CustomerUpdate.csproj │ │ ├── IWriteService.cs │ │ └── WriteService.cs │ │ ├── Admin.Extensibility │ │ ├── Admin.Extensibility.csproj │ │ └── Hook.cs │ │ └── Admin.sln └── tests │ ├── Admin.UnitTests │ ├── Admin.UnitTests.csproj │ └── ExtensibilityTests.cs │ └── CommitCustomerData.Extended.UnitTests │ ├── CommitCustomerData.Extended.UnitTests.csproj │ └── CommitCustomerDataTests.cs ├── chapter-6-travel-rep └── src │ ├── .dockerignore │ ├── TravelRep.Ambassador │ ├── CentralSystemProxyService.cs │ ├── Dockerfile │ ├── Models │ │ ├── Cancellation.cs │ │ ├── Complaint.cs │ │ ├── Location.cs │ │ └── SystemConfiguration.cs │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── TravelRep.Ambassador.csproj │ ├── appsettings.Development.json │ ├── appsettings.json │ ├── hf-log.db │ ├── hf.db │ └── mkcert │ ├── TravelRep.App │ ├── Dockerfile │ ├── Models │ │ ├── Cancellation.cs │ │ ├── Complaint.cs │ │ ├── Location.cs │ │ └── SystemConfiguration.cs │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── TravelRep.App.csproj │ └── appsettings.json │ ├── TravelRep.CentralApi │ ├── Program.cs │ ├── Properties │ │ └── launchSettings.json │ ├── TravelRep.CentralApi.csproj │ ├── appsettings.Development.json │ ├── appsettings.json │ ├── localhost-host.docker.internal-key.pem │ └── localhost-host.docker.internal.pem │ ├── TravelRep.sln │ └── docker-compose.yml └── readme.md /chapter-1-ticket-sales/src/TicketSales.Api/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to find out which attributes exist for C# debugging 3 | // Use hover for the description of the existing attributes 4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": ".NET Core Launch (web)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | // If you have changed target frameworks, make sure to update the program path. 13 | "program": "${workspaceFolder}/bin/Debug/netcoreapp3.1/TicketSales.Api.dll", 14 | "args": [], 15 | "cwd": "${workspaceFolder}", 16 | "stopAtEntry": false, 17 | // Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser 18 | "serverReadyAction": { 19 | "action": "openExternally", 20 | "pattern": "\\bNow listening on:\\s+(https?://\\S+)" 21 | }, 22 | "env": { 23 | "ASPNETCORE_ENVIRONMENT": "Development" 24 | }, 25 | "sourceFileMap": { 26 | "/Views": "${workspaceFolder}/Views" 27 | } 28 | }, 29 | { 30 | "name": ".NET Core Attach", 31 | "type": "coreclr", 32 | "request": "attach", 33 | "processId": "${command:pickProcess}" 34 | } 35 | ] 36 | } -------------------------------------------------------------------------------- /chapter-1-ticket-sales/src/TicketSales.Api/.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}/TicketSales.Api.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}/TicketSales.Api.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}/TicketSales.Api.csproj", 36 | "/property:GenerateFullPaths=true", 37 | "/consoleloggerparameters:NoSummary" 38 | ], 39 | "problemMatcher": "$msCompile" 40 | } 41 | ] 42 | } -------------------------------------------------------------------------------- /chapter-1-ticket-sales/src/TicketSales.Api/Controllers/TicketInventoryController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using TicketSales.Common; 5 | using TicketSales.ThirdPartyProxy; 6 | 7 | namespace TicketSales.Api.Controllers 8 | { 9 | [ApiController] 10 | [Route("[controller]")] 11 | public class TicketInventoryController 12 | { 13 | private readonly ITicketService _ticketService; 14 | 15 | public TicketInventoryController(ITicketService ticketService) 16 | { 17 | _ticketService = ticketService; 18 | } 19 | 20 | [HttpGet] 21 | public async Task> GetTickets() 22 | { 23 | var result = await _ticketService.GetTickets(); 24 | if (result.IsSuccess) 25 | { 26 | return result.Data; 27 | } 28 | else 29 | { 30 | // Log Error 31 | return null; 32 | } 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /chapter-1-ticket-sales/src/TicketSales.Api/Controllers/TicketOrderController.cs: -------------------------------------------------------------------------------- 1 | using System.Threading.Tasks; 2 | using Microsoft.AspNetCore.Mvc; 3 | using Microsoft.Extensions.Logging; 4 | using TicketSales.Common; 5 | using TicketSales.ThirdPartyProxy; 6 | 7 | namespace TicketSales.Api.Controllers 8 | { 9 | [ApiController] 10 | [Route("[controller]/[action]")] 11 | public class TicketOrderController : ControllerBase 12 | { 13 | private readonly ILogger _logger; 14 | private readonly ITicketService _ticketService; 15 | 16 | public TicketOrderController(ILogger logger, 17 | ITicketService ticketService) 18 | { 19 | _logger = logger; 20 | _ticketService = ticketService; 21 | } 22 | 23 | [HttpPost] 24 | public async Task OrderTicket(TicketInformation ticketInformation) 25 | { 26 | _logger.Log(LogLevel.Information, "OrderTicket"); 27 | 28 | bool isSuccess = await _ticketService.OrderTicket(ticketInformation); 29 | return isSuccess; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /chapter-1-ticket-sales/src/TicketSales.Api/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Hosting; 6 | using Microsoft.Extensions.Configuration; 7 | using Microsoft.Extensions.Hosting; 8 | using Microsoft.Extensions.Logging; 9 | 10 | namespace TicketSales.Api 11 | { 12 | public class Program 13 | { 14 | public static void Main(string[] args) 15 | { 16 | CreateHostBuilder(args).Build().Run(); 17 | } 18 | 19 | public static IHostBuilder CreateHostBuilder(string[] args) => 20 | Host.CreateDefaultBuilder(args) 21 | .ConfigureWebHostDefaults(webBuilder => 22 | { 23 | webBuilder.UseStartup(); 24 | }); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /chapter-1-ticket-sales/src/TicketSales.Api/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:19346", 8 | "sslPort": 44359 9 | } 10 | }, 11 | "profiles": { 12 | "IIS Express": { 13 | "commandName": "IISExpress", 14 | "launchBrowser": true, 15 | "launchUrl": "ticketinventory", 16 | "environmentVariables": { 17 | "ASPNETCORE_ENVIRONMENT": "Development" 18 | } 19 | }, 20 | "TicketSales.Api": { 21 | "commandName": "Project", 22 | "launchBrowser": true, 23 | "launchUrl": "ticketinventory", 24 | "applicationUrl": "https://localhost:5101;http://localhost:5100", 25 | "environmentVariables": { 26 | "ASPNETCORE_ENVIRONMENT": "Development" 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /chapter-1-ticket-sales/src/TicketSales.Api/Startup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.AspNetCore.Hosting; 3 | using Microsoft.Extensions.Configuration; 4 | using Microsoft.Extensions.DependencyInjection; 5 | using Microsoft.Extensions.Hosting; 6 | using System; 7 | using TicketSales.Common; 8 | using TicketSales.ServiceBusHelper; 9 | using TicketSales.ThirdPartyProxy; 10 | 11 | namespace TicketSales.Api 12 | { 13 | public class Startup 14 | { 15 | public Startup(IConfiguration configuration) 16 | { 17 | Configuration = configuration; 18 | } 19 | 20 | public IConfiguration Configuration { get; } 21 | 22 | // This method gets called by the runtime. Use this method to add services to the container. 23 | public void ConfigureServices(IServiceCollection services) 24 | { 25 | services.AddControllers(); 26 | 27 | services.AddSingleton((s) => new ServiceBusConfiguration() 28 | { 29 | ConnectionString = Configuration.GetValue("ServiceBus:ConnectionString"), 30 | QueueName = Configuration.GetValue("ServiceBus:QueueName") 31 | }); 32 | 33 | services.AddSingleton((s) => new TicketServiceConfiguration() 34 | { 35 | Endpoint = Configuration.GetValue("ExternalTicketBookingEndpoint") 36 | }); 37 | 38 | services.AddScoped(); 39 | services.AddScoped(); 40 | services.AddScoped((a) => 41 | { 42 | return new ConsoleHelper("Api", ConsoleColor.White); 43 | }); 44 | services.AddScoped(); 45 | 46 | services.AddHttpClient(); 47 | } 48 | 49 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 50 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 51 | { 52 | if (env.IsDevelopment()) 53 | { 54 | app.UseDeveloperExceptionPage(); 55 | } 56 | 57 | app.UseHttpsRedirection(); 58 | 59 | app.UseRouting(); 60 | 61 | /* 62 | app.Use(next => context => 63 | { 64 | Console.WriteLine($"Found: {context.GetEndpoint()?.DisplayName}"); 65 | return next(context); 66 | }); 67 | */ 68 | 69 | app.UseAuthorization(); 70 | 71 | app.UseEndpoints(endpoints => 72 | { 73 | endpoints.MapControllers(); 74 | }); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /chapter-1-ticket-sales/src/TicketSales.Api/TicketSales.Api.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netcoreapp3.1 5 | TicketSales.Api 6 | 36bd66a3-8d00-4fc7-9520-533c6fe7fbbf 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /chapter-1-ticket-sales/src/TicketSales.Api/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /chapter-1-ticket-sales/src/TicketSales.Api/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "AllowedHosts": "*", 10 | "ServiceBus": { 11 | "QueueName": "TicketSalesQueue" 12 | }, 13 | "ExternalTicketBookingEndpoint": "https://localhost:44355/externalticketbooking" 14 | } 15 | -------------------------------------------------------------------------------- /chapter-1-ticket-sales/src/TicketSales.Common/ConsoleHelper.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace TicketSales.Common 4 | { 5 | public class ConsoleHelper 6 | { 7 | private readonly string _id; 8 | private readonly ConsoleColor _consoleColor; 9 | 10 | public ConsoleHelper(string id, ConsoleColor consoleColor) 11 | { 12 | _id = id; 13 | _consoleColor = consoleColor; 14 | } 15 | 16 | public void OutputTime() 17 | { 18 | OutputId(); 19 | 20 | Console.ForegroundColor = ConsoleColor.DarkBlue; 21 | Console.WriteLine($"Date Stamp: {DateTime.Now}"); 22 | Console.ResetColor(); 23 | } 24 | 25 | private void OutputId() 26 | { 27 | Console.ForegroundColor = _consoleColor; 28 | Console.Write($"({_id}) : "); 29 | } 30 | 31 | public void OutputString(string message) 32 | { 33 | OutputId(); 34 | 35 | Console.ForegroundColor = ConsoleColor.DarkGreen; 36 | Console.WriteLine($"{DateTime.Now}: {message}"); 37 | Console.ResetColor(); 38 | } 39 | 40 | public void OutputWarning(string message) 41 | { 42 | OutputId(); 43 | 44 | Console.ForegroundColor = ConsoleColor.Red; 45 | Console.WriteLine($"{DateTime.Now}: {message}"); 46 | Console.ResetColor(); 47 | } 48 | 49 | public void AwaitKeyPress(string prompt = "Please press any key to continue") 50 | { 51 | OutputId(); 52 | 53 | Console.ForegroundColor = ConsoleColor.Green; 54 | Console.WriteLine(prompt); 55 | Console.ReadKey(); 56 | Console.ResetColor(); 57 | } 58 | 59 | public ConsoleKeyInfo GetKeyPress(string mainPrompt, string[] prompts) 60 | { 61 | OutputId(); 62 | 63 | Console.ForegroundColor = ConsoleColor.Green; 64 | Console.WriteLine(mainPrompt); 65 | 66 | foreach (string prompt in prompts) 67 | { 68 | Console.WriteLine(prompt); 69 | } 70 | 71 | Console.ResetColor(); 72 | return Console.ReadKey(); 73 | } 74 | 75 | } 76 | } -------------------------------------------------------------------------------- /chapter-1-ticket-sales/src/TicketSales.Common/ConsoleLogger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace TicketSales.Common 6 | { 7 | public class ConsoleLogger : ILogger 8 | { 9 | private readonly ConsoleHelper _consoleHelper; 10 | 11 | public ConsoleLogger(ConsoleHelper consoleHelper) 12 | { 13 | _consoleHelper = consoleHelper; 14 | } 15 | 16 | public void Log(string message) 17 | { 18 | _consoleHelper.OutputString(message); 19 | } 20 | 21 | public void LogError(Exception exception) 22 | { 23 | _consoleHelper.OutputWarning(exception.Message); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /chapter-1-ticket-sales/src/TicketSales.Common/ILogger.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace TicketSales.Common 4 | { 5 | public interface ILogger 6 | { 7 | void Log(string message); 8 | void LogError(Exception exception); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /chapter-1-ticket-sales/src/TicketSales.Common/Models/DataResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace TicketSales.Common.Models 6 | { 7 | public class DataResult 8 | { 9 | public T Data { get; set; } 10 | public bool IsSuccess { get; set; } 11 | public string[]? Errors { get; set; } 12 | 13 | public static DataResult Success(T data) => 14 | new DataResult() 15 | { 16 | IsSuccess = true, 17 | Data = data 18 | }; 19 | 20 | public static DataResult Failure(string error) => 21 | new DataResult() 22 | { 23 | IsSuccess = false, 24 | Errors = new[] { error } 25 | }; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /chapter-1-ticket-sales/src/TicketSales.Common/Models/TicketInformation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace TicketSales.Common 4 | { 5 | public class TicketInformation 6 | { 7 | public string? EventCode { get; set; } 8 | 9 | public DateTime EventDate { get; set; } 10 | 11 | public decimal Price { get; set; } 12 | 13 | public string? SeatCode { get; set; } 14 | 15 | public int Quantity { get; set; } 16 | 17 | public string ClientId { get; set; } 18 | 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /chapter-1-ticket-sales/src/TicketSales.Common/TicketSales.Common.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | netstandard2.1 5 | enable 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /chapter-1-ticket-sales/src/TicketSales.MockClient/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to find out which attributes exist for C# debugging 3 | // Use hover for the description of the existing attributes 4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": ".NET Core Launch (console)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | // If you have changed target frameworks, make sure to update the program path. 13 | "program": "${workspaceFolder}/bin/Debug/netcoreapp3.1/mock-client.dll", 14 | "args": [], 15 | "cwd": "${workspaceFolder}", 16 | // For more information about the 'console' field, see https://aka.ms/VSCode-CS-LaunchJson-Console 17 | "console": "internalConsole", 18 | "stopAtEntry": false 19 | }, 20 | { 21 | "name": ".NET Core Attach", 22 | "type": "coreclr", 23 | "request": "attach", 24 | "processId": "${command:pickProcess}" 25 | } 26 | ] 27 | } -------------------------------------------------------------------------------- /chapter-1-ticket-sales/src/TicketSales.MockClient/.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}/mock-client.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}/mock-client.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}/mock-client.csproj", 36 | "/property:GenerateFullPaths=true", 37 | "/consoleloggerparameters:NoSummary" 38 | ], 39 | "problemMatcher": "$msCompile" 40 | } 41 | ] 42 | } -------------------------------------------------------------------------------- /chapter-1-ticket-sales/src/TicketSales.MockClient/TicketInformation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace TicketSales.MockClient 4 | { 5 | public class TicketInformation 6 | { 7 | public string? EventCode { get; set; } 8 | 9 | public DateTime EventDate { get; set; } 10 | 11 | public decimal Price { get; set; } 12 | 13 | public string? SeatCode { get; set; } 14 | 15 | public int Quantity { get; set; } 16 | 17 | public string ClientId { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /chapter-1-ticket-sales/src/TicketSales.MockClient/TicketSales.MockClient.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | TicketSales.MockClient 7 | enable 8 | 9b603669-dc43-4296-aa4a-55e0fcedd281 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | PreserveNewest 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | -------------------------------------------------------------------------------- /chapter-1-ticket-sales/src/TicketSales.MockClient/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ServiceBus": { 3 | "QueueName": "TicketSalesQueue", 4 | "ResponseQueueName": "TicketSalesQueueResponse" 5 | } 6 | 7 | } 8 | -------------------------------------------------------------------------------- /chapter-1-ticket-sales/src/TicketSales.Proxy/Controllers/BookingController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.Extensions.Logging; 7 | 8 | namespace TicketSales.Proxy.Controllers 9 | { 10 | [ApiController] 11 | [Route("[controller]")] 12 | public class BookingController : ControllerBase 13 | { 14 | private readonly ILogger _logger; 15 | 16 | public BookingController(ILogger logger) 17 | { 18 | _logger = logger; 19 | } 20 | 21 | [HttpGet] 22 | public IEnumerable Get() 23 | { 24 | // Call Api 25 | 26 | // Manually deserialise the result 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /chapter-1-ticket-sales/src/TicketSales.Proxy/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Hosting; 6 | using Microsoft.Extensions.Configuration; 7 | using Microsoft.Extensions.Hosting; 8 | using Microsoft.Extensions.Logging; 9 | 10 | namespace TicketSales.Proxy 11 | { 12 | public class Program 13 | { 14 | public static void Main(string[] args) 15 | { 16 | CreateHostBuilder(args).Build().Run(); 17 | } 18 | 19 | public static IHostBuilder CreateHostBuilder(string[] args) => 20 | Host.CreateDefaultBuilder(args) 21 | .ConfigureWebHostDefaults(webBuilder => 22 | { 23 | webBuilder.UseStartup(); 24 | }); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /chapter-1-ticket-sales/src/TicketSales.Proxy/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:25979", 8 | "sslPort": 44383 9 | } 10 | }, 11 | "profiles": { 12 | "IIS Express": { 13 | "commandName": "IISExpress", 14 | "launchBrowser": true, 15 | "launchUrl": "weatherforecast", 16 | "environmentVariables": { 17 | "ASPNETCORE_ENVIRONMENT": "Development" 18 | } 19 | }, 20 | "TicketSales.Proxy": { 21 | "commandName": "Project", 22 | "launchBrowser": true, 23 | "launchUrl": "weatherforecast", 24 | "applicationUrl": "https://localhost:5001;http://localhost:5000", 25 | "environmentVariables": { 26 | "ASPNETCORE_ENVIRONMENT": "Development" 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /chapter-1-ticket-sales/src/TicketSales.Proxy/Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Builder; 6 | using Microsoft.AspNetCore.Hosting; 7 | using Microsoft.AspNetCore.HttpsPolicy; 8 | using Microsoft.AspNetCore.Mvc; 9 | using Microsoft.Extensions.Configuration; 10 | using Microsoft.Extensions.DependencyInjection; 11 | using Microsoft.Extensions.Hosting; 12 | using Microsoft.Extensions.Logging; 13 | 14 | namespace TicketSales.Proxy 15 | { 16 | public class Startup 17 | { 18 | public Startup(IConfiguration configuration) 19 | { 20 | Configuration = configuration; 21 | } 22 | 23 | public IConfiguration Configuration { get; } 24 | 25 | // This method gets called by the runtime. Use this method to add services to the container. 26 | public void ConfigureServices(IServiceCollection services) 27 | { 28 | services.AddControllers(); 29 | } 30 | 31 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 32 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 33 | { 34 | if (env.IsDevelopment()) 35 | { 36 | app.UseDeveloperExceptionPage(); 37 | } 38 | 39 | app.UseHttpsRedirection(); 40 | 41 | app.UseRouting(); 42 | 43 | app.UseAuthorization(); 44 | 45 | app.UseEndpoints(endpoints => 46 | { 47 | endpoints.MapControllers(); 48 | }); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /chapter-1-ticket-sales/src/TicketSales.Proxy/TicketInformation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace TicketSales.Proxy 4 | { 5 | public class TicketInformation 6 | { 7 | public string EventCode { get; set; } 8 | 9 | public DateTime EventDate { get; set; } 10 | 11 | public decimal Price { get; set; } 12 | 13 | public string SeatCode { get; set; } 14 | 15 | public int Quantity { get; set; } 16 | 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /chapter-1-ticket-sales/src/TicketSales.Proxy/TicketSales.Proxy.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /chapter-1-ticket-sales/src/TicketSales.Proxy/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /chapter-1-ticket-sales/src/TicketSales.Proxy/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "AllowedHosts": "*" 10 | } 11 | -------------------------------------------------------------------------------- /chapter-1-ticket-sales/src/TicketSales.Service/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Azure.ServiceBus; 2 | using Microsoft.Extensions.Configuration; 3 | using Newtonsoft.Json; 4 | using System; 5 | using System.Net.Http; 6 | using System.Text; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | using TicketSales.Common; 10 | using TicketSales.ServiceBusHelper; 11 | 12 | 13 | namespace TicketSales.Service 14 | { 15 | class Program 16 | { 17 | private static ServiceBusConfiguration _serviceBusConfiguration; 18 | private static string _ticketSalesApiEndpoint; 19 | private static HttpClient _httpClient; 20 | private static IQueueHelper _queueHelper; 21 | private static Guid _clientId = Guid.NewGuid(); 22 | private static ConsoleHelper _consoleHelper; 23 | private static ConsoleLogger _consoleLogger; 24 | 25 | static void Main(string[] args) 26 | { 27 | ReadConfiguration(); 28 | _httpClient = new HttpClient(); 29 | _consoleHelper = new ConsoleHelper("Service", ConsoleColor.Yellow); 30 | _consoleLogger = new ConsoleLogger(_consoleHelper); 31 | 32 | _queueHelper = new QueueHelper( 33 | _serviceBusConfiguration, 34 | _consoleLogger); 35 | 36 | _queueHelper.Listen(onMessageReceived, false); 37 | 38 | _consoleHelper.AwaitKeyPress("Please press any key to exit"); 39 | } 40 | 41 | private static async Task onMessageReceived(Message message, CancellationToken cancellationToken) 42 | { 43 | var messageBody = Encoding.UTF8.GetString(message.Body, 0, message.Body.Length); 44 | _consoleHelper.OutputString($"Correlation Id: {message.CorrelationId} Message: {messageBody}"); 45 | 46 | // Deserialise and serialise because these objects are held separately 47 | var ticketInformation = JsonConvert.DeserializeObject(messageBody); 48 | 49 | bool result = await CallOrderTicket(ticketInformation); 50 | 51 | // Put a message back on the queue to indicate the result 52 | var response = new TicketCreationResponse() 53 | { 54 | IsSuccess = result 55 | }; 56 | 57 | await _queueHelper.AddNewMessage( 58 | JsonConvert.SerializeObject(response), 59 | message.CorrelationId); 60 | 61 | await _queueHelper.CompleteReceivedMessage(message); 62 | } 63 | 64 | private static async Task CallOrderTicket(TicketInformation ticketInformation) 65 | { 66 | var info = new StringContent( 67 | JsonConvert.SerializeObject(ticketInformation), 68 | Encoding.UTF8, 69 | "application/json"); 70 | HttpResponseMessage response = await _httpClient.PostAsync( 71 | $"{_ticketSalesApiEndpoint}/orderticket", info); 72 | return (response.IsSuccessStatusCode); 73 | } 74 | 75 | private static void ReadConfiguration() 76 | { 77 | var configuration = new ConfigurationBuilder() 78 | .AddJsonFile("appsettings.json", true, true) 79 | .AddUserSecrets() 80 | .Build(); 81 | 82 | _serviceBusConfiguration = new ServiceBusConfiguration() 83 | { 84 | ConnectionString = configuration.GetValue("ServiceBus:ConnectionString"), 85 | QueueName = configuration.GetValue("ServiceBus:QueueName"), 86 | ResponseQueueName = configuration.GetValue("ServiceBus:ResponseQueueName") 87 | }; 88 | 89 | _ticketSalesApiEndpoint = configuration.GetValue("TicketSalesApiEndpoint"); 90 | } 91 | } 92 | } 93 | -------------------------------------------------------------------------------- /chapter-1-ticket-sales/src/TicketSales.Service/TicketCreationResponse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace TicketSales.Service 6 | { 7 | public class TicketCreationResponse 8 | { 9 | public bool IsSuccess { get; set; } 10 | public string? Error { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /chapter-1-ticket-sales/src/TicketSales.Service/TicketInformation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace TicketSales.Service 4 | { 5 | public class TicketInformation 6 | { 7 | public string ClientId { get; set; } 8 | 9 | public string? EventCode { get; set; } 10 | 11 | public DateTime EventDate { get; set; } 12 | 13 | public decimal Price { get; set; } 14 | 15 | public string? SeatCode { get; set; } 16 | 17 | public int Quantity { get; set; } 18 | 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /chapter-1-ticket-sales/src/TicketSales.Service/TicketSales.Service.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | dc9aa1ef-bd00-43e3-8666-7ce4b1bec1f6 7 | enable 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | PreserveNewest 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /chapter-1-ticket-sales/src/TicketSales.Service/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ServiceBus": { 3 | "QueueName": "TicketSalesQueue", 4 | "ResponseQueueName": "TicketSalesQueueResponse" 5 | }, 6 | "TicketSalesApiEndpoint": "https://localhost:5101/ticketorder" 7 | } 8 | -------------------------------------------------------------------------------- /chapter-1-ticket-sales/src/TicketSales.ServiceBusHelper/Configuration/ServiceBusConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace TicketSales.ServiceBusHelper 7 | { 8 | public class ServiceBusConfiguration 9 | { 10 | public string ConnectionString { get; set; } 11 | public string QueueName { get; set; } 12 | public string ResponseQueueName { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /chapter-1-ticket-sales/src/TicketSales.ServiceBusHelper/IQueueHelper.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Azure.ServiceBus; 2 | using System; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | namespace TicketSales.ServiceBusHelper 7 | { 8 | public interface IQueueHelper 9 | { 10 | Task AddNewMessage(string messageBody, string correlationId = ""); 11 | Task GetMessageByCorrelationId(string correlationId); 12 | Task SendMessageAwaitReply(string messageBody); 13 | void Listen( 14 | Func onMessageReceived, 15 | bool autoComplete); 16 | Task CompleteReceivedMessage(Message message); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /chapter-1-ticket-sales/src/TicketSales.ServiceBusHelper/QueueHelper.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Azure.ServiceBus; 2 | using System; 3 | using System.Diagnostics; 4 | using System.Text; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using TicketSales.Common; 8 | 9 | namespace TicketSales.ServiceBusHelper 10 | { 11 | public class QueueHelper : IQueueHelper, IAsyncDisposable 12 | { 13 | private readonly IQueueClient _responseQueueClient; 14 | private readonly IQueueClient _sendQueueClient; 15 | 16 | private readonly ILogger _logger; 17 | 18 | public QueueHelper(ServiceBusConfiguration serviceBusConfiguration, 19 | ILogger logger) 20 | { 21 | _sendQueueClient = new QueueClient( 22 | serviceBusConfiguration.ConnectionString, 23 | serviceBusConfiguration.QueueName, 24 | ReceiveMode.PeekLock); 25 | _responseQueueClient = new QueueClient( 26 | serviceBusConfiguration.ConnectionString, 27 | serviceBusConfiguration.ResponseQueueName, 28 | ReceiveMode.PeekLock); 29 | _logger = logger; 30 | } 31 | 32 | public async Task AddNewMessage(string messageBody, string correlationId = "") 33 | { 34 | var message = new Message(Encoding.UTF8.GetBytes(messageBody)) 35 | { 36 | CorrelationId = string.IsNullOrWhiteSpace(correlationId) ? Guid.NewGuid().ToString() : correlationId 37 | }; 38 | 39 | await _sendQueueClient.SendAsync(message); 40 | 41 | _logger.Log($"AddNewMessage: {message.CorrelationId}"); 42 | 43 | return message.CorrelationId; 44 | } 45 | 46 | public async Task SendMessageAwaitReply(string messageBody) 47 | { 48 | var correlationId = await AddNewMessage(messageBody); 49 | var result = await GetMessageByCorrelationId(correlationId); 50 | 51 | return result; 52 | } 53 | 54 | public async ValueTask DisposeAsync() 55 | { 56 | await _sendQueueClient.CloseAsync(); 57 | } 58 | 59 | public async Task GetMessageByCorrelationId(string correlationId) 60 | { 61 | _logger.Log($"GetMessageByCorrelationId: {correlationId}"); 62 | 63 | var tcs = new TaskCompletionSource(); 64 | string returnMessageBody = string.Empty; 65 | 66 | var messageHandlerOptions = new MessageHandlerOptions(ExceptionReceivedHandler) 67 | { 68 | AutoComplete = false 69 | }; 70 | 71 | _responseQueueClient.RegisterMessageHandler(async (message, cancellationToken) => 72 | { 73 | _logger.Log($"Received Message: {message.CorrelationId}, To: {message.To}, ReplyTo: {message.ReplyTo}"); 74 | if (message.CorrelationId == correlationId) 75 | { 76 | returnMessageBody = Encoding.UTF8.GetString(message.Body, 0, message.Body.Length); 77 | 78 | await _responseQueueClient.CompleteAsync(message.SystemProperties.LockToken); 79 | _logger.Log($"Accepted Message: {returnMessageBody}"); 80 | 81 | tcs.TrySetResult(message); 82 | } 83 | else 84 | { 85 | await _responseQueueClient.AbandonAsync(message.SystemProperties.LockToken); 86 | } 87 | }, messageHandlerOptions); 88 | 89 | await tcs.Task; 90 | return returnMessageBody; 91 | } 92 | 93 | private Task ExceptionReceivedHandler(ExceptionReceivedEventArgs arg) 94 | { 95 | _logger.LogError(arg.Exception); 96 | return Task.CompletedTask; 97 | } 98 | 99 | public void Listen( 100 | Func onMessageReceived, 101 | bool autoComplete) 102 | { 103 | _logger.Log("TicketSales.Service.Listen"); 104 | 105 | string returnMessageBody = string.Empty; 106 | 107 | var messageHandlerOptions = new MessageHandlerOptions(ExceptionReceivedHandler) 108 | { 109 | AutoComplete = autoComplete 110 | }; 111 | 112 | _sendQueueClient.RegisterMessageHandler(onMessageReceived, messageHandlerOptions); 113 | } 114 | 115 | public async Task CompleteReceivedMessage(Message message) 116 | { 117 | await _sendQueueClient.CompleteAsync(message.SystemProperties.LockToken); 118 | } 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /chapter-1-ticket-sales/src/TicketSales.ServiceBusHelper/TicketSales.ServiceBusHelper.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /chapter-1-ticket-sales/src/TicketSales.ThirdPartyApi/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // Use IntelliSense to find out which attributes exist for C# debugging 3 | // Use hover for the description of the existing attributes 4 | // For further information visit https://github.com/OmniSharp/omnisharp-vscode/blob/master/debugger-launchjson.md 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "name": ".NET Core Launch (web)", 9 | "type": "coreclr", 10 | "request": "launch", 11 | "preLaunchTask": "build", 12 | // If you have changed target frameworks, make sure to update the program path. 13 | "program": "${workspaceFolder}/bin/Debug/netcoreapp3.1/booking-api.dll", 14 | "args": [], 15 | "cwd": "${workspaceFolder}", 16 | "stopAtEntry": false, 17 | // Enable launching a web browser when ASP.NET Core starts. For more information: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser 18 | "serverReadyAction": { 19 | "action": "openExternally", 20 | "pattern": "\\bNow listening on:\\s+(https?://\\S+)" 21 | }, 22 | "env": { 23 | "ASPNETCORE_ENVIRONMENT": "Development" 24 | }, 25 | "sourceFileMap": { 26 | "/Views": "${workspaceFolder}/Views" 27 | } 28 | }, 29 | { 30 | "name": ".NET Core Attach", 31 | "type": "coreclr", 32 | "request": "attach", 33 | "processId": "${command:pickProcess}" 34 | } 35 | ] 36 | } -------------------------------------------------------------------------------- /chapter-1-ticket-sales/src/TicketSales.ThirdPartyApi/.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}/booking-api.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}/booking-api.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}/booking-api.csproj", 36 | "/property:GenerateFullPaths=true", 37 | "/consoleloggerparameters:NoSummary" 38 | ], 39 | "problemMatcher": "$msCompile" 40 | } 41 | ] 42 | } -------------------------------------------------------------------------------- /chapter-1-ticket-sales/src/TicketSales.ThirdPartyApi/Controllers/ExternalTicketBookingController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading.Tasks; 4 | using Microsoft.AspNetCore.Mvc; 5 | 6 | namespace TicketSales.ThirdPartyApi.Controllers 7 | { 8 | [ApiController] 9 | [Route("[controller]/[action]")] 10 | public class ExternalTicketBookingController : ControllerBase 11 | { 12 | private static Random _random = new Random(); 13 | private static int _capacity = 1200000; 14 | private static string _eventCode = "GLS_21"; 15 | private static DateTime _eventDate = new DateTime(2021, 06, 23); 16 | private static decimal[] _prices = new [] { 17 | 100.50m, 260.65m, 540.10m 18 | }; 19 | 20 | [HttpGet] 21 | public IEnumerable GetTickets() 22 | { 23 | var tickets = new List(); 24 | 25 | for (int i = 0; i < _prices.Length; i++) 26 | { 27 | tickets.Add(new TicketInformation() 28 | { 29 | EventCode = _eventCode, 30 | EventDate = _eventDate, 31 | Price = _prices[_random.Next(_prices.Length)], 32 | SeatCode = "NA", 33 | Quantity = _capacity / _prices.Length 34 | }); 35 | } 36 | 37 | return tickets; 38 | } 39 | 40 | [HttpPost("{seatCode?}")] 41 | public async Task ReserveTicket(string seatCode) 42 | { 43 | await Task.Delay(1000); 44 | 45 | if (_random.Next(10) == 1) 46 | { 47 | return BadRequest(); 48 | } 49 | return Ok(); 50 | } 51 | 52 | [HttpPost("{seatCode?}")] 53 | public async Task PurchaseTicket(string seatCode) 54 | { 55 | await Task.Delay(2000); 56 | 57 | if (_random.Next(10) == 1) 58 | { 59 | return BadRequest(); 60 | } 61 | return Ok(); 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /chapter-1-ticket-sales/src/TicketSales.ThirdPartyApi/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Hosting; 6 | using Microsoft.Extensions.Configuration; 7 | using Microsoft.Extensions.Hosting; 8 | using Microsoft.Extensions.Logging; 9 | 10 | namespace TicketSales.ThirdPartyApi 11 | { 12 | public class Program 13 | { 14 | public static void Main(string[] args) 15 | { 16 | CreateHostBuilder(args).Build().Run(); 17 | } 18 | 19 | public static IHostBuilder CreateHostBuilder(string[] args) => 20 | Host.CreateDefaultBuilder(args) 21 | .ConfigureWebHostDefaults(webBuilder => 22 | { 23 | webBuilder.UseStartup(); 24 | }); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /chapter-1-ticket-sales/src/TicketSales.ThirdPartyApi/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:54096", 8 | "sslPort": 44355 9 | } 10 | }, 11 | "profiles": { 12 | "IIS Express": { 13 | "commandName": "IISExpress", 14 | "launchBrowser": true, 15 | "launchUrl": "externalticketbooking", 16 | "environmentVariables": { 17 | "ASPNETCORE_ENVIRONMENT": "Development" 18 | } 19 | }, 20 | "TicketSales.ThirdPartyApi": { 21 | "commandName": "Project", 22 | "launchBrowser": true, 23 | "launchUrl": "externalticketbooking", 24 | "applicationUrl": "https://localhost:5001;http://localhost:5000", 25 | "environmentVariables": { 26 | "ASPNETCORE_ENVIRONMENT": "Development" 27 | } 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /chapter-1-ticket-sales/src/TicketSales.ThirdPartyApi/Startup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | using Microsoft.AspNetCore.Builder; 6 | using Microsoft.AspNetCore.Hosting; 7 | using Microsoft.AspNetCore.HttpsPolicy; 8 | using Microsoft.AspNetCore.Mvc; 9 | using Microsoft.Extensions.Configuration; 10 | using Microsoft.Extensions.DependencyInjection; 11 | using Microsoft.Extensions.Hosting; 12 | using Microsoft.Extensions.Logging; 13 | 14 | namespace TicketSales.ThirdPartyApi 15 | { 16 | public class Startup 17 | { 18 | public Startup(IConfiguration configuration) 19 | { 20 | Configuration = configuration; 21 | } 22 | 23 | public IConfiguration Configuration { get; } 24 | 25 | // This method gets called by the runtime. Use this method to add services to the container. 26 | public void ConfigureServices(IServiceCollection services) 27 | { 28 | services.AddControllers(); 29 | } 30 | 31 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 32 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 33 | { 34 | if (env.IsDevelopment()) 35 | { 36 | app.UseDeveloperExceptionPage(); 37 | } 38 | 39 | app.UseHttpsRedirection(); 40 | 41 | app.UseRouting(); 42 | 43 | app.UseAuthorization(); 44 | 45 | app.UseEndpoints(endpoints => 46 | { 47 | endpoints.MapControllers(); 48 | }); 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /chapter-1-ticket-sales/src/TicketSales.ThirdPartyApi/TicketInformation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace TicketSales.ThirdPartyApi 4 | { 5 | public class TicketInformation 6 | { 7 | public string? EventCode { get; set; } 8 | 9 | public DateTime EventDate { get; set; } 10 | 11 | public decimal Price { get; set; } 12 | 13 | public string? SeatCode { get; set; } 14 | 15 | public int Quantity { get; set; } 16 | 17 | public string ClientId { get; set; } 18 | 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /chapter-1-ticket-sales/src/TicketSales.ThirdPartyApi/TicketSales.ThirdPartyApi.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | TicketSales.ThirdPartyApi 6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /chapter-1-ticket-sales/src/TicketSales.ThirdPartyApi/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /chapter-1-ticket-sales/src/TicketSales.ThirdPartyApi/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "AllowedHosts": "*" 10 | } 11 | -------------------------------------------------------------------------------- /chapter-1-ticket-sales/src/TicketSales.ThirdPartyApi/readme.md: -------------------------------------------------------------------------------- 1 | # Purpose 2 | 3 | Simulates a third-party ticket API. 4 | 5 | 6 | # Notes 7 | 8 | The service has a random failure build into it, along with an artificial delay to simulate a real world system. -------------------------------------------------------------------------------- /chapter-1-ticket-sales/src/TicketSales.ThirdPartyProxy/ITicketService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | using TicketSales.Common; 6 | using TicketSales.Common.Models; 7 | 8 | namespace TicketSales.ThirdPartyProxy 9 | { 10 | public interface ITicketService 11 | { 12 | Task>> GetTickets(); 13 | Task OrderTicket(TicketInformation ticketInformation); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /chapter-1-ticket-sales/src/TicketSales.ThirdPartyProxy/TicketSales.ThirdPartyProxy.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netstandard2.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /chapter-1-ticket-sales/src/TicketSales.ThirdPartyProxy/TicketService.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Net.Http; 4 | using System.Runtime.Serialization.Json; 5 | using System.Threading.Tasks; 6 | using TicketSales.Common; 7 | using System.Text.Json; 8 | using System.Text.Json.Serialization; 9 | using TicketSales.Common.Models; 10 | 11 | namespace TicketSales.ThirdPartyProxy 12 | { 13 | public class TicketService : ITicketService 14 | { 15 | private readonly IHttpClientFactory _httpClientFactory; 16 | private readonly TicketServiceConfiguration _ticketServiceConfiguration; 17 | 18 | public TicketService(IHttpClientFactory httpClientFactory, 19 | TicketServiceConfiguration ticketServiceConfiguration) 20 | { 21 | _httpClientFactory = httpClientFactory; 22 | _ticketServiceConfiguration = ticketServiceConfiguration; 23 | } 24 | 25 | public async Task>> GetTickets() 26 | { 27 | var client = _httpClientFactory.CreateClient(); 28 | 29 | HttpResponseMessage response = await client.GetAsync( 30 | $"{_ticketServiceConfiguration.Endpoint}/GetTickets"); 31 | 32 | if (response.IsSuccessStatusCode) 33 | { 34 | string result = await response.Content.ReadAsStringAsync(); 35 | 36 | var options = new JsonSerializerOptions() 37 | { 38 | PropertyNamingPolicy = JsonNamingPolicy.CamelCase 39 | }; 40 | var data = JsonSerializer.Deserialize>(result, options); 41 | 42 | return DataResult>.Success(data); 43 | } 44 | else 45 | { 46 | // Log error 47 | return DataResult>.Failure($"Error: {response.ReasonPhrase}"); 48 | } 49 | } 50 | 51 | public async Task OrderTicket(TicketInformation ticketInformation) 52 | { 53 | var client = _httpClientFactory.CreateClient(); 54 | 55 | HttpResponseMessage response = await client.PostAsync( 56 | $"{_ticketServiceConfiguration.Endpoint}/PurchaseTicket", 57 | new StringContent(string.Empty)); 58 | 59 | return (response.IsSuccessStatusCode); 60 | 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /chapter-1-ticket-sales/src/TicketSales.ThirdPartyProxy/TicketServiceConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace TicketSales.ThirdPartyProxy 6 | { 7 | public class TicketServiceConfiguration 8 | { 9 | public string Endpoint { get; set; } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /chapter-2-cash-desk/src/CashDesk/CashDesk.Client/CashDesk.Client.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /chapter-2-cash-desk/src/CashDesk/CashDesk.Client/Program.cs: -------------------------------------------------------------------------------- 1 | using CashDesk.DataPersistence; 2 | using CashDesk.Server; 3 | using CashDesk.Server.Entities; 4 | using CashDesk.Server.EventTypes; 5 | using CashDesk.Server.Persistence; 6 | using Newtonsoft.Json; 7 | using Newtonsoft.Json.Linq; 8 | using System; 9 | using System.Linq; 10 | 11 | namespace CashDesk.Client 12 | { 13 | class Program 14 | { 15 | private static DataEventsPersistence _dataEvents = new DataEventsPersistence(); 16 | private static PersistData _persistData = new PersistData(_dataEvents); 17 | private static CashDeskEventStream _cashDeskEventStream; 18 | 19 | static void Main(string[] args) 20 | { 21 | _cashDeskEventStream = _persistData.Load("Stream1"); 22 | 23 | while (true) 24 | { 25 | Console.WriteLine("1 - Add till transaction"); 26 | Console.WriteLine("2 - View till balance"); 27 | Console.WriteLine("3 - View all till balances"); 28 | Console.WriteLine("4 - View all transactions"); 29 | Console.WriteLine("5 - Exit"); 30 | 31 | var key = Console.ReadKey(); 32 | 33 | switch (key.Key) 34 | { 35 | case ConsoleKey.D1: 36 | AddTransactionDialog(); 37 | break; 38 | 39 | case ConsoleKey.D2: 40 | GetBalanceForTillDialog(); 41 | break; 42 | 43 | case ConsoleKey.D3: 44 | GetBalanceForAllTillsDialog(); 45 | break; 46 | 47 | case ConsoleKey.D4: 48 | GetTransactionHistory(); 49 | break; 50 | 51 | default: 52 | _persistData.Save(_cashDeskEventStream); 53 | return; 54 | } 55 | } 56 | } 57 | 58 | private static void GetTransactionHistory() 59 | { 60 | foreach (var eachEvent in _cashDeskEventStream.Changes) 61 | { 62 | var cashDeskTransactionAddedEvent = (CashDeskTransactionAddedEvent)eachEvent; 63 | var transaction = (CashDeskTransaction)cashDeskTransactionAddedEvent.Transaction; 64 | 65 | Console.WriteLine($"{transaction.DateStamp}: {transaction.CashDeskId}" + 66 | $" - {transaction.ProductCode} ({transaction.Amount})"); 67 | } 68 | 69 | } 70 | 71 | private static void GetBalanceForAllTillsDialog() 72 | { 73 | decimal balanceTotal = _cashDeskEventStream.CashDeskBalance.Sum(a => a.Value); 74 | 75 | Console.WriteLine($"Balance across the whole estate is {balanceTotal}"); 76 | } 77 | 78 | private static void GetBalanceForTillDialog() 79 | { 80 | Console.Write("Cash Desk Id: "); 81 | string cashDeskId = Console.ReadLine(); 82 | 83 | decimal balance = GetBalanceFor(cashDeskId); 84 | Console.WriteLine($"Balance for {cashDeskId} is {balance.ToString()}"); 85 | } 86 | 87 | private static void AddTransactionDialog() 88 | { 89 | Console.Write("Cash Desk Id: "); 90 | string cashDeskId = Console.ReadLine(); 91 | 92 | Console.Write("Product Code: "); 93 | string productCode = Console.ReadLine(); 94 | 95 | Console.Write("Description: "); 96 | string description = Console.ReadLine(); 97 | 98 | Console.Write("Amount: "); 99 | string amount = Console.ReadLine(); 100 | 101 | AddTransaction(productCode, description, amount, cashDeskId); 102 | } 103 | 104 | private static decimal GetBalanceFor(string cashDeskId) => 105 | _cashDeskEventStream.CashDeskBalance[cashDeskId]; 106 | 107 | static void AddTransaction(string productCode, string description, 108 | string amountString, string cashDeskId) 109 | { 110 | decimal amount = Decimal.Parse(amountString); 111 | 112 | var transaction = new CashDeskTransaction(DateTime.Now, 113 | productCode, description, amount, cashDeskId); 114 | 115 | _cashDeskEventStream.AddTransaction(transaction); 116 | } 117 | } 118 | } 119 | -------------------------------------------------------------------------------- /chapter-2-cash-desk/src/CashDesk/CashDesk.DataPersistence/CashDesk.DataPersistence.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /chapter-2-cash-desk/src/CashDesk/CashDesk.DataPersistence/DataEventsPersistence.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.IO; 4 | using System.Linq; 5 | 6 | namespace CashDesk.DataPersistence 7 | { 8 | public class DataEventsPersistence : IDataEventsPersistence 9 | { 10 | public void Append(string streamName, string dataStream) 11 | { 12 | if (!Directory.Exists("data")) 13 | { 14 | Directory.CreateDirectory("data"); 15 | } 16 | 17 | string fileName = $"data/{DateTime.UtcNow.ToString("yyyy-MM-dd-hh-mm-ss")}-{streamName}"; 18 | 19 | using var file = new StreamWriter(fileName, true); 20 | file.Write(dataStream); 21 | } 22 | 23 | public string[] Read(string streamName) 24 | { 25 | if (!Directory.Exists("data")) 26 | { 27 | return new[] { string.Empty }; 28 | } 29 | 30 | List returnList = new List(); 31 | 32 | foreach (var file in Directory.GetFiles("data")) 33 | { 34 | if (!file.Contains(streamName)) 35 | continue; 36 | 37 | string streamText = File.ReadAllText(file); 38 | returnList.Add(streamText); 39 | } 40 | 41 | return returnList.OrderBy(a => a).ToArray(); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /chapter-2-cash-desk/src/CashDesk/CashDesk.DataPersistence/IDataEventsPersistence.cs: -------------------------------------------------------------------------------- 1 | namespace CashDesk.DataPersistence 2 | { 3 | public interface IDataEventsPersistence 4 | { 5 | void Append(string streamName, string dataStream); 6 | string[] Read(string streamName); 7 | } 8 | } -------------------------------------------------------------------------------- /chapter-2-cash-desk/src/CashDesk/CashDesk.Server/CashDesk.Server.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /chapter-2-cash-desk/src/CashDesk/CashDesk.Server/CashDeskEventStream.cs: -------------------------------------------------------------------------------- 1 | using CashDesk.Server.Entities; 2 | using CashDesk.Server.EventTypes; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | 7 | namespace CashDesk.Server 8 | { 9 | public class CashDeskEventStream : EventStreamBase 10 | { 11 | public Dictionary CashDeskBalance { get; set; } = 12 | new Dictionary(); 13 | 14 | public void AddTransaction(CashDeskTransaction transaction) 15 | { 16 | 17 | var trans = new CashDeskTransactionAddedEvent() 18 | { 19 | Transaction = transaction, 20 | IsNew = true 21 | }; 22 | Apply(trans); 23 | } 24 | 25 | protected override void When(object theEvent) 26 | { 27 | switch (theEvent) 28 | { 29 | case CashDeskTransactionAddedEvent transactionEvent: 30 | var transaction = (CashDeskTransaction)transactionEvent.Transaction; 31 | 32 | if (!CashDeskBalance.ContainsKey(transaction.CashDeskId)) 33 | CashDeskBalance[transaction.CashDeskId] = 0; 34 | 35 | CashDeskBalance[transaction.CashDeskId] += transaction.Amount; 36 | break; 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /chapter-2-cash-desk/src/CashDesk/CashDesk.Server/Entities/CashDeskTransaction.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace CashDesk.Server.Entities 6 | { 7 | public class CashDeskTransaction : ITransaction 8 | { 9 | public CashDeskTransaction(DateTime dateTime, string productCode, string description, decimal amount, string cashDeskId) 10 | { 11 | DateStamp = dateTime; 12 | ProductCode = productCode; 13 | Description = description; 14 | Amount = amount; 15 | CashDeskId = cashDeskId; 16 | } 17 | 18 | public DateTime DateStamp { get; set; } 19 | public string ProductCode { get; set; } 20 | public string Description { get; set; } 21 | public decimal Amount { get; set; } 22 | public string CashDeskId { get; set; } 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /chapter-2-cash-desk/src/CashDesk/CashDesk.Server/Entities/CashDeskTransactionConverter.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using System.Transactions; 6 | 7 | namespace CashDesk.Server.Entities 8 | { 9 | public class CashDeskTransactionConverter : JsonConverter 10 | { 11 | public override bool CanConvert(Type objectType) 12 | { 13 | return (objectType == typeof(ITransaction)); 14 | } 15 | 16 | public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) 17 | { 18 | return serializer.Deserialize(reader, typeof(CashDeskTransaction)); 19 | } 20 | 21 | public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer) 22 | { 23 | serializer.Serialize(writer, value, typeof(CashDeskTransaction)); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /chapter-2-cash-desk/src/CashDesk/CashDesk.Server/Entities/ITransaction.cs: -------------------------------------------------------------------------------- 1 | namespace CashDesk.Server.Entities 2 | { 3 | public interface ITransaction 4 | { 5 | } 6 | } -------------------------------------------------------------------------------- /chapter-2-cash-desk/src/CashDesk/CashDesk.Server/EventStreamBase.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json; 2 | using Newtonsoft.Json.Linq; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Diagnostics.CodeAnalysis; 6 | using System.Text; 7 | 8 | namespace CashDesk.Server 9 | { 10 | public abstract class EventStreamBase 11 | { 12 | public string StreamName { get; set; } 13 | public List Changes = new List(); 14 | 15 | public void Apply(object theEvent) 16 | { 17 | When(theEvent); 18 | Changes.Add(theEvent); 19 | } 20 | 21 | protected abstract void When(object theEvent); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /chapter-2-cash-desk/src/CashDesk/CashDesk.Server/EventTypes/CashDeskTransactionAddedEvent.cs: -------------------------------------------------------------------------------- 1 | using CashDesk.Server.Entities; 2 | using Newtonsoft.Json; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Text; 6 | 7 | namespace CashDesk.Server.EventTypes 8 | { 9 | public class CashDeskTransactionAddedEvent : IEvent 10 | { 11 | public ITransaction Transaction { get; set; } 12 | 13 | [JsonIgnore] 14 | public bool IsNew { get; set; } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /chapter-2-cash-desk/src/CashDesk/CashDesk.Server/EventTypes/IEvent.cs: -------------------------------------------------------------------------------- 1 | using CashDesk.Server.Entities; 2 | 3 | namespace CashDesk.Server.EventTypes 4 | { 5 | public interface IEvent 6 | { 7 | ITransaction Transaction { get; set; } 8 | bool IsNew { get; set; } 9 | } 10 | } -------------------------------------------------------------------------------- /chapter-2-cash-desk/src/CashDesk/CashDesk.Server/Persistence/DataCashDeskTransactionAddedEvent.cs: -------------------------------------------------------------------------------- 1 | using CashDesk.Server.EventTypes; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace CashDesk.Server.Persistence 7 | { 8 | public class DataCashDeskTransactionAddedEvent : CashDeskTransactionAddedEvent, IDataEvent 9 | { 10 | public DataCashDeskTransactionAddedEvent() { } 11 | 12 | public DataCashDeskTransactionAddedEvent(string eventType) 13 | { 14 | EventType = eventType; 15 | } 16 | 17 | public string EventType { get; set; } 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /chapter-2-cash-desk/src/CashDesk/CashDesk.Server/Persistence/IDataEvent.cs: -------------------------------------------------------------------------------- 1 | using CashDesk.Server.EventTypes; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace CashDesk.Server.Persistence 7 | { 8 | public interface IDataEvent : IEvent 9 | { 10 | public string EventType { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /chapter-2-cash-desk/src/CashDesk/CashDesk.Server/Persistence/PersistData.cs: -------------------------------------------------------------------------------- 1 | using CashDesk.DataPersistence; 2 | using CashDesk.Server.Entities; 3 | using CashDesk.Server.EventTypes; 4 | using Newtonsoft.Json; 5 | using Newtonsoft.Json.Linq; 6 | using System; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | 10 | namespace CashDesk.Server.Persistence 11 | { 12 | public class PersistData where TEventStreamBase : EventStreamBase, new() 13 | { 14 | private readonly IDataEventsPersistence _dataEvents; 15 | 16 | public PersistData(IDataEventsPersistence dataEvents) 17 | { 18 | _dataEvents = dataEvents; 19 | } 20 | 21 | public void Save(TEventStreamBase eventStream) 22 | where TDataEvent : IDataEvent, new() 23 | where TEvent : IEvent 24 | { 25 | var changes = eventStream.Changes; 26 | if (changes == null || !changes.Any()) 27 | { 28 | return; 29 | } 30 | 31 | var dataStream = new List(); 32 | foreach (var change in changes) 33 | { 34 | var eventChange = (TEvent)change; 35 | if (!eventChange.IsNew) continue; 36 | 37 | var dataEvent = new TDataEvent() 38 | { 39 | Transaction = eventChange.Transaction, 40 | EventType = typeof(TDataEvent).AssemblyQualifiedName 41 | }; 42 | dataStream.Add(dataEvent); 43 | } 44 | var saveChanges = new SaveChanges() 45 | { 46 | Changes = dataStream.Select(a => (object)a).ToList(), 47 | StreamName = eventStream.StreamName 48 | }; 49 | 50 | var dataStreamSerialised = JsonConvert.SerializeObject(saveChanges); 51 | 52 | _dataEvents.Append(eventStream.StreamName, dataStreamSerialised); 53 | } 54 | 55 | public TEventStreamBase Load(string streamName) 56 | { 57 | var dataEventsSerialised = _dataEvents.Read(streamName); 58 | var dataEvents = new List(); 59 | 60 | foreach (var dataEventSerialised in dataEventsSerialised) 61 | { 62 | var dataEvent = JsonConvert.DeserializeObject(dataEventSerialised); 63 | dataEvents.Add(new TEventStreamBase() 64 | { 65 | Changes = dataEvent.Changes, 66 | StreamName = dataEvent.StreamName 67 | }); 68 | } 69 | 70 | if (dataEvents == null || !dataEvents.Any() 71 | || (!dataEvents.Where(a => a?.Changes?.Any() ?? false)?.Any() ?? false)) 72 | { 73 | var newStream = new TEventStreamBase() 74 | { 75 | StreamName = streamName 76 | }; 77 | 78 | return newStream; 79 | } 80 | 81 | var eventStream = new TEventStreamBase() 82 | { 83 | StreamName = dataEvents.First().StreamName 84 | }; 85 | 86 | foreach (var dataEvent in dataEvents) 87 | { 88 | foreach (var eachEvent in dataEvent.Changes) 89 | { 90 | var obj = JObject.Parse(eachEvent.ToString()); 91 | var eventType = obj["EventType"].Value(); 92 | var type = Type.GetType(eventType); 93 | 94 | if (type == typeof(DataCashDeskTransactionAddedEvent)) 95 | { 96 | // https://www.pmichaels.net/tag/type-is-an-interface-or-abstract-class-and-cannot-be-instantiated/ 97 | var settings = new JsonSerializerSettings(); 98 | settings.Converters.Add(new CashDeskTransactionConverter()); 99 | 100 | var deserialisedObject = JsonConvert.DeserializeObject(eachEvent.ToString(), type, settings); 101 | 102 | eventStream.Apply(deserialisedObject); 103 | } 104 | } 105 | } 106 | return eventStream; 107 | } 108 | } 109 | } 110 | -------------------------------------------------------------------------------- /chapter-2-cash-desk/src/CashDesk/CashDesk.Server/Persistence/SaveChanges.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace CashDesk.Server.Persistence 6 | { 7 | public class SaveChanges 8 | { 9 | public List Changes { get; set; } 10 | public string StreamName { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /chapter-2-cash-desk/src/CashDesk/CashDesk.UnitTesks/CashDesk.Tesks.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | all 13 | runtime; build; native; contentfiles; analyzers; buildtransitive 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /chapter-2-cash-desk/src/CashDesk/CashDesk.UnitTesks/Integration/Persistence.cs: -------------------------------------------------------------------------------- 1 | using CashDesk.Server.EventTypes; 2 | using CashDesk.Server.Persistence; 3 | using CashDesk.UnitTesks.Mocks; 4 | using NSubstitute; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Text; 9 | using Xunit; 10 | 11 | namespace CashDesk.UnitTesks.Integration 12 | { 13 | public class Persistence 14 | { 15 | 16 | [Fact] 17 | public void PersistData_LoadStream_CanReadSaveStream() 18 | { 19 | // Arrange 20 | string streamName = "EventStream1"; 21 | 22 | var cashDeskStream = new CashDesk.Server.CashDeskEventStream() 23 | { 24 | StreamName = "EventStream1" 25 | }; 26 | 27 | var dataEventsPersistence = new DataEventsPersistenceMock(); 28 | 29 | cashDeskStream.AddTransaction(new Server.Entities.CashDeskTransaction( 30 | new DateTime(2021, 05, 05), "HAMMER", "Test", 20.12m, "1")); 31 | 32 | var dataPersistence = new PersistData(dataEventsPersistence); 33 | 34 | // Act 35 | dataPersistence.Save(cashDeskStream); 36 | var result = dataPersistence.Load(streamName); 37 | 38 | // Assert 39 | Assert.Equal(streamName, result.StreamName); 40 | Assert.Equal(20.12m, result.CashDeskBalance["1"]); 41 | } 42 | 43 | [Fact] 44 | public void PersistData_LoadStream_SaveStream_MultipleTimes() 45 | { 46 | // Arrange 47 | string streamName = "EventStream1"; 48 | 49 | var cashDeskStream = new CashDesk.Server.CashDeskEventStream() 50 | { 51 | StreamName = "EventStream1" 52 | }; 53 | 54 | var dataEventsPersistence = new DataEventsPersistenceMock(); 55 | 56 | cashDeskStream.AddTransaction(new Server.Entities.CashDeskTransaction( 57 | new DateTime(2021, 05, 05), "HAMMER", "Test", 20.12m, "1")); 58 | 59 | var dataPersistence = new PersistData(dataEventsPersistence); 60 | 61 | // Act 62 | dataPersistence.Save(cashDeskStream); 63 | var result = dataPersistence.Load(streamName); 64 | 65 | cashDeskStream.AddTransaction(new Server.Entities.CashDeskTransaction( 66 | new DateTime(2021, 05, 05), "NAIL", "Test", 5.12m, "1")); 67 | 68 | dataPersistence.Save(cashDeskStream); 69 | result = dataPersistence.Load(streamName); 70 | 71 | cashDeskStream.AddTransaction(new Server.Entities.CashDeskTransaction( 72 | new DateTime(2021, 05, 05), "SCREWDRIVER", "Test", 8.55m, "1")); 73 | 74 | dataPersistence.Save(cashDeskStream); 75 | result = dataPersistence.Load(streamName); 76 | 77 | // Assert 78 | Assert.Equal(streamName, result.StreamName); 79 | Assert.Equal(33.79m, result.CashDeskBalance["1"]); 80 | } 81 | 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /chapter-2-cash-desk/src/CashDesk/CashDesk.UnitTesks/Mocks/DataEventsPersistenceMock.cs: -------------------------------------------------------------------------------- 1 | using CashDesk.DataPersistence; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace CashDesk.UnitTesks.Mocks 7 | { 8 | class DataEventsPersistenceMock : IDataEventsPersistence 9 | { 10 | // Hold the data in memory to simulate the read and write to disk 11 | private string _streamName; 12 | private string _dataStream; 13 | 14 | public void Append(string streamName, string dataStream) 15 | { 16 | _streamName = streamName; 17 | _dataStream = dataStream; 18 | } 19 | 20 | public string[] Read(string streamName) 21 | { 22 | if (_streamName != streamName) 23 | { 24 | throw new Exception($"Stream name should be {_streamName} but is {streamName}"); 25 | } 26 | 27 | return new[] { _dataStream }; 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /chapter-2-cash-desk/src/CashDesk/CashDesk.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30803.129 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CashDesk.Client", "CashDesk.Client\CashDesk.Client.csproj", "{DF700E63-F365-46FF-BA8F-893496910B52}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CashDesk.Server", "CashDesk.Server\CashDesk.Server.csproj", "{B7AD9EFD-3E12-4BAB-94E7-BBF9F9FCAA54}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CashDesk.Tesks", "CashDesk.UnitTesks\CashDesk.Tesks.csproj", "{1636EA82-F73F-4B3E-BEA6-8294C50F6C3C}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CashDesk.DataPersistence", "CashDesk.DataPersistence\CashDesk.DataPersistence.csproj", "{46BA9B93-C4CA-4CF2-B2DD-86A7F833D1C4}" 13 | EndProject 14 | Global 15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 16 | Debug|Any CPU = Debug|Any CPU 17 | Release|Any CPU = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 20 | {DF700E63-F365-46FF-BA8F-893496910B52}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {DF700E63-F365-46FF-BA8F-893496910B52}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {DF700E63-F365-46FF-BA8F-893496910B52}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {DF700E63-F365-46FF-BA8F-893496910B52}.Release|Any CPU.Build.0 = Release|Any CPU 24 | {B7AD9EFD-3E12-4BAB-94E7-BBF9F9FCAA54}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {B7AD9EFD-3E12-4BAB-94E7-BBF9F9FCAA54}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {B7AD9EFD-3E12-4BAB-94E7-BBF9F9FCAA54}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {B7AD9EFD-3E12-4BAB-94E7-BBF9F9FCAA54}.Release|Any CPU.Build.0 = Release|Any CPU 28 | {1636EA82-F73F-4B3E-BEA6-8294C50F6C3C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {1636EA82-F73F-4B3E-BEA6-8294C50F6C3C}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {1636EA82-F73F-4B3E-BEA6-8294C50F6C3C}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {1636EA82-F73F-4B3E-BEA6-8294C50F6C3C}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {46BA9B93-C4CA-4CF2-B2DD-86A7F833D1C4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {46BA9B93-C4CA-4CF2-B2DD-86A7F833D1C4}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {46BA9B93-C4CA-4CF2-B2DD-86A7F833D1C4}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {46BA9B93-C4CA-4CF2-B2DD-86A7F833D1C4}.Release|Any CPU.Build.0 = Release|Any CPU 36 | EndGlobalSection 37 | GlobalSection(SolutionProperties) = preSolution 38 | HideSolutionNode = FALSE 39 | EndGlobalSection 40 | GlobalSection(ExtensibilityGlobals) = postSolution 41 | SolutionGuid = {1BF1F8F2-12E8-44A9-A572-63A273B6E330} 42 | EndGlobalSection 43 | EndGlobal 44 | -------------------------------------------------------------------------------- /chapter-3-travel-agent/src/TravelAgent/TravelAgent/TravelAgent.Client/BookingRequest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace TravelAgent.Client 6 | { 7 | public class BookingRequest 8 | { 9 | public string Type { get; set; } = "BookingRequest"; 10 | public DateTime Date { get; set; } 11 | public int GuestCount { get; set; } 12 | public string Function { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /chapter-3-travel-agent/src/TravelAgent/TravelAgent/TravelAgent.Client/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Azure.ServiceBus; 2 | using Microsoft.Extensions.Configuration; 3 | using Newtonsoft.Json; 4 | using System; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace TravelAgent.Client 9 | { 10 | class Program 11 | { 12 | static async Task Main(string[] args) 13 | { 14 | IConfiguration configuration = new ConfigurationBuilder() 15 | .AddJsonFile("appsettings.json", true, true) 16 | .AddUserSecrets() 17 | .Build(); 18 | 19 | string connectionString = configuration.GetValue("ConnectionString"); 20 | 21 | while (true) 22 | { 23 | Console.WriteLine("Client Simulator: Choose action"); 24 | Console.WriteLine("1 - Make single booking"); 25 | Console.WriteLine("2 - Make 100 bookings"); 26 | Console.WriteLine("3 - Exit"); 27 | var action = Console.ReadKey(); 28 | 29 | switch (action.Key) 30 | { 31 | case ConsoleKey.D1: 32 | await StartBooking(connectionString); 33 | break; 34 | 35 | case ConsoleKey.D2: 36 | for (int i = 1; i <= 100; i++) 37 | { 38 | await StartBooking(connectionString); 39 | } 40 | break; 41 | 42 | case ConsoleKey.D3: 43 | return; 44 | } 45 | Console.WriteLine("Test complete..."); 46 | } 47 | } 48 | 49 | private static async Task StartBooking(string connectionString) 50 | { 51 | var bookingRequest = new BookingRequest() 52 | { 53 | Date = DateTime.Now, 54 | GuestCount = 1, 55 | Function = "Book" 56 | }; 57 | 58 | string messageBody = JsonConvert.SerializeObject(bookingRequest); 59 | await SendMessage(connectionString, messageBody); 60 | } 61 | 62 | static async Task SendMessage(string connectionString, string messageBody) 63 | { 64 | var queueClient = new QueueClient(connectionString, "BookingQueue"); 65 | 66 | var message = new Message(Encoding.UTF8.GetBytes(messageBody)); 67 | 68 | await queueClient.SendAsync(message); 69 | await queueClient.CloseAsync(); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /chapter-3-travel-agent/src/TravelAgent/TravelAgent/TravelAgent.Client/TravelAgent.Client.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp3.1 6 | e5f6d972-da27-4ddc-baa3-dcf06e423c30 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | PreserveNewest 20 | 21 | 22 | 23 | 24 | -------------------------------------------------------------------------------- /chapter-3-travel-agent/src/TravelAgent/TravelAgent/TravelAgent.Client/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionString": "" 3 | } -------------------------------------------------------------------------------- /chapter-3-travel-agent/src/TravelAgent/TravelAgent/TravelAgent.Coordinator/ApiProxies/HospitalProxy.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json.Linq; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Net.Http; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace TravelAgent.Coordinator 9 | { 10 | public class HospitalProxy 11 | { 12 | private IHttpClientFactory _httpClientFactory; 13 | 14 | public HospitalProxy(IHttpClientFactory httpClientFactory) 15 | { 16 | _httpClientFactory = httpClientFactory; 17 | } 18 | 19 | public async Task CallHospitalApi(DateTime date) => 20 | await BookHospitalRoom(date); 21 | 22 | private async Task BookHospitalRoom(DateTime date) 23 | { 24 | var client = _httpClientFactory.CreateClient(); 25 | 26 | var requestContent = new StringContent(@"{ 'RoomRequest': { 'Date': '" + date.ToString("yyyy-MM-dd") + "' } }"); 27 | 28 | var result = await client.PostAsync("https://localhost:5001/book", requestContent); 29 | return result.IsSuccessStatusCode; 30 | } 31 | 32 | public async Task CancelHospitalBooking(DateTime date) 33 | { 34 | var client = _httpClientFactory.CreateClient(); 35 | 36 | var requestContent = new StringContent(@"{ 'CancellationRequest': { 'Date': '" + date.ToString("yyyy-MM-dd") + "' } }"); 37 | 38 | var result = await client.PostAsync("https://localhost:5001/cancel", requestContent); 39 | return result.IsSuccessStatusCode; 40 | 41 | } 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /chapter-3-travel-agent/src/TravelAgent/TravelAgent/TravelAgent.Coordinator/ApiProxies/HotelProxy.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json.Linq; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Net.Http; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace TravelAgent.Coordinator 9 | { 10 | public class HotelProxy 11 | { 12 | private IHttpClientFactory _httpClientFactory; 13 | 14 | public HotelProxy(IHttpClientFactory httpClientFactory) 15 | { 16 | _httpClientFactory = httpClientFactory; 17 | } 18 | 19 | public async Task CallHotelApi(DateTime date) 20 | { 21 | bool isAvailable = await GetHotelAvailability(date); 22 | if (isAvailable) 23 | { 24 | return await BookHotelRoom(date); 25 | } 26 | 27 | return false; 28 | } 29 | 30 | private async Task GetHotelAvailability(DateTime dateTime) 31 | { 32 | var client = _httpClientFactory.CreateClient(); 33 | 34 | string dateParam = dateTime.ToString("yyyy-MM-dd"); 35 | var result = await client.GetAsync($"https://localhost:5020/Availability?date={dateParam}"); 36 | if (result.IsSuccessStatusCode) 37 | { 38 | var content = await result.Content.ReadAsStringAsync(); 39 | var jobject = JObject.Parse(content); 40 | var available = jobject.SelectToken("roomCount"); 41 | bool isAvailable = available.Value() >= 1; 42 | 43 | return isAvailable; 44 | } 45 | 46 | return false; 47 | } 48 | 49 | private async Task BookHotelRoom(DateTime date) 50 | { 51 | var client = _httpClientFactory.CreateClient(); 52 | 53 | string requestString = "{ \"date\": \"" + date.ToString("yyyy-MM-ddTHH:mm:ssZ") + "\", \"roomType\": 1 }"; 54 | 55 | var requestContent = new StringContent( 56 | requestString, 57 | Encoding.UTF8, 58 | "application/json"); 59 | var result = await client.PostAsync("https://localhost:5020/book", requestContent); 60 | return result.IsSuccessStatusCode; 61 | } 62 | 63 | internal async Task CancelHotelRoom(DateTime date) 64 | { 65 | var client = _httpClientFactory.CreateClient(); 66 | 67 | string requestString = "{ \"date\": \"" + date.ToString("yyyy-MM-ddTHH:mm:ssZ") + "\", \"roomType\": 1 }"; 68 | 69 | var requestContent = new StringContent( 70 | requestString, 71 | Encoding.UTF8, 72 | "application/json"); 73 | var result = await client.PostAsync("https://localhost:5020/cancel", requestContent); 74 | return result.IsSuccessStatusCode; 75 | 76 | } 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /chapter-3-travel-agent/src/TravelAgent/TravelAgent/TravelAgent.Coordinator/ApiProxies/SpaceFlightProxy.cs: -------------------------------------------------------------------------------- 1 | using Newtonsoft.Json.Linq; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Net.Http; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace TravelAgent.Coordinator 9 | { 10 | public class SpaceFlightProxy 11 | { 12 | private IHttpClientFactory _httpClientFactory; 13 | 14 | public SpaceFlightProxy(IHttpClientFactory httpClientFactory) 15 | { 16 | _httpClientFactory = httpClientFactory; 17 | } 18 | 19 | public async Task CallSpaceFlightApi(DateTime date) => 20 | await BookSpaceFlight(date); 21 | 22 | private async Task BookSpaceFlight(DateTime date) 23 | { 24 | var client = _httpClientFactory.CreateClient(); 25 | 26 | string requestString = "{ \"date\": \"" + date.ToString("yyyy-MM-ddTHH:mm:ssZ") + "\", \"roomType\": 1 }"; 27 | 28 | var requestContent = new StringContent( 29 | requestString, 30 | Encoding.UTF8, 31 | "application/json"); 32 | var result = await client.PostAsync("https://localhost:5101/book", requestContent); 33 | return result.IsSuccessStatusCode; 34 | } 35 | 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /chapter-3-travel-agent/src/TravelAgent/TravelAgent/TravelAgent.Coordinator/BookingRequest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace TravelAgent.Client 6 | { 7 | public class BookingRequest 8 | { 9 | public string Type { get; set; } = "BookingRequest"; 10 | public DateTime Date { get; set; } 11 | public int GuestCount { get; set; } 12 | public string Function { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /chapter-3-travel-agent/src/TravelAgent/TravelAgent/TravelAgent.Coordinator/IBookingRequestHandler.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace TravelAgent.Coordinator 5 | { 6 | public interface IBookingRequestHandler 7 | { 8 | Task ProcessBookingRequest(string requestType, DateTime date, string function); 9 | 10 | } 11 | } -------------------------------------------------------------------------------- /chapter-3-travel-agent/src/TravelAgent/TravelAgent/TravelAgent.Coordinator/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Azure.ServiceBus; 2 | using Microsoft.Extensions.Configuration; 3 | using Microsoft.Extensions.DependencyInjection; 4 | using Microsoft.Extensions.Http; 5 | using Newtonsoft.Json.Linq; 6 | using System; 7 | using System.Net.Http; 8 | using System.Text; 9 | using System.Threading; 10 | using System.Threading.Tasks; 11 | 12 | namespace TravelAgent.Coordinator 13 | { 14 | class Program 15 | { 16 | private static IHttpClientFactory _httpClientFactory; 17 | private static IBookingRequestHandler _bookingRequestHandler; 18 | private static QueueClient _queueClient; 19 | 20 | static void Main(string[] args) 21 | { 22 | Console.WriteLine("Transaction Coordinator: Start Process - press any key"); 23 | Console.ReadKey(); 24 | 25 | IConfiguration configuration = new ConfigurationBuilder() 26 | .AddJsonFile("appsettings.json", true, true) 27 | .AddUserSecrets() 28 | .Build(); 29 | 30 | string connectionString = configuration.GetValue("ConnectionString"); 31 | 32 | _queueClient = new QueueClient(connectionString, "BookingQueue"); 33 | 34 | var messageHandlerOptions = new MessageHandlerOptions(ExceptionHandler); 35 | messageHandlerOptions.AutoComplete = false; 36 | 37 | _queueClient.RegisterMessageHandler(handleMessage, messageHandlerOptions); 38 | 39 | var serviceProvider = new ServiceCollection() 40 | .AddSingleton(srv => 41 | { 42 | return new BookingRequestHandler(connectionString, srv.GetService()); 43 | }) 44 | .AddHttpClient() 45 | .BuildServiceProvider(); 46 | _httpClientFactory = serviceProvider.GetService(); 47 | _bookingRequestHandler = serviceProvider.GetService(); 48 | 49 | Console.WriteLine("Coordinator started..."); 50 | Console.WriteLine("Press any key to exit"); 51 | Console.ReadKey(); 52 | } 53 | 54 | private static Task ExceptionHandler(ExceptionReceivedEventArgs arg) 55 | { 56 | Console.WriteLine("Something bad happened!"); 57 | return Task.CompletedTask; 58 | } 59 | 60 | private static async Task handleMessage(Message message, CancellationToken cancellation) 61 | { 62 | string messageBody = Encoding.UTF8.GetString(message.Body); 63 | Console.WriteLine("Message received: {0}", messageBody); 64 | 65 | var json = JObject.Parse(messageBody); 66 | string type = json.SelectToken("Type").Value(); 67 | DateTime date = json.SelectToken("Date").Value(); 68 | string function = json.SelectToken("Function").Value(); 69 | 70 | await _bookingRequestHandler.ProcessBookingRequest(type, date, function); 71 | await _queueClient.CompleteAsync(message.SystemProperties.LockToken); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /chapter-3-travel-agent/src/TravelAgent/TravelAgent/TravelAgent.Coordinator/TravelAgent.Coordinator.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net5.0 6 | 8c4da6dd-e45b-4dbd-96cd-e3894bc43d5d 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | PreserveNewest 22 | 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /chapter-3-travel-agent/src/TravelAgent/TravelAgent/TravelAgent.Coordinator/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "ConnectionString": "" 3 | } -------------------------------------------------------------------------------- /chapter-3-travel-agent/src/TravelAgent/TravelAgent/TravelAgent.HospitalApi/Controllers/HospitalTestController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Microsoft.Extensions.Logging; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | 8 | namespace TravelAgent.HospitalApi.Controllers 9 | { 10 | [ApiController] 11 | [Route("[controller]")] 12 | public class HospitalTestController : ControllerBase 13 | { 14 | private Random _rnd = new Random(); 15 | private readonly ILogger _logger; 16 | 17 | public HospitalTestController(ILogger logger) 18 | { 19 | _logger = logger; 20 | } 21 | 22 | [HttpPost] 23 | [Route("/book")] 24 | public async Task BookHospitalTest() 25 | { 26 | await Task.Delay(_rnd.Next(5000)); // Simulate slow response 27 | 28 | if (_rnd.Next(10) == 1) 29 | { 30 | return BadRequest(); 31 | } 32 | return Ok(); 33 | } 34 | 35 | [HttpPost] 36 | [Route("/cancel")] 37 | public async Task CancelHospitalTest() 38 | { 39 | await Task.Delay(_rnd.Next(5000)); // Simulate slow response 40 | return Ok(); 41 | } 42 | 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /chapter-3-travel-agent/src/TravelAgent/TravelAgent/TravelAgent.HospitalApi/Models/RoomRequest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace TravelAgent.HospitalApi.Models 4 | { 5 | public class HospitalTestRequest 6 | { 7 | public DateTime Date { get; set; } 8 | 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /chapter-3-travel-agent/src/TravelAgent/TravelAgent/TravelAgent.HospitalApi/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.Extensions.Configuration; 3 | using Microsoft.Extensions.Hosting; 4 | using Microsoft.Extensions.Logging; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Threading.Tasks; 9 | 10 | namespace TravelAgent.HospitalApi 11 | { 12 | public class Program 13 | { 14 | public static void Main(string[] args) 15 | { 16 | CreateHostBuilder(args).Build().Run(); 17 | } 18 | 19 | public static IHostBuilder CreateHostBuilder(string[] args) => 20 | Host.CreateDefaultBuilder(args) 21 | .ConfigureWebHostDefaults(webBuilder => 22 | { 23 | webBuilder.UseStartup(); 24 | }); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /chapter-3-travel-agent/src/TravelAgent/TravelAgent/TravelAgent.HospitalApi/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:60345", 8 | "sslPort": 44369 9 | } 10 | }, 11 | "profiles": { 12 | "IIS Express": { 13 | "commandName": "IISExpress", 14 | "launchBrowser": true, 15 | "launchUrl": "swagger", 16 | "environmentVariables": { 17 | "ASPNETCORE_ENVIRONMENT": "Development" 18 | } 19 | }, 20 | "TravelAgent.HospitalApi": { 21 | "commandName": "Project", 22 | "dotnetRunMessages": "true", 23 | "launchBrowser": true, 24 | "launchUrl": "swagger", 25 | "applicationUrl": "https://localhost:5001;http://localhost:5000", 26 | "environmentVariables": { 27 | "ASPNETCORE_ENVIRONMENT": "Development" 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /chapter-3-travel-agent/src/TravelAgent/TravelAgent/TravelAgent.HospitalApi/Startup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.AspNetCore.Hosting; 3 | using Microsoft.AspNetCore.HttpsPolicy; 4 | using Microsoft.AspNetCore.Mvc; 5 | using Microsoft.Extensions.Configuration; 6 | using Microsoft.Extensions.DependencyInjection; 7 | using Microsoft.Extensions.Hosting; 8 | using Microsoft.Extensions.Logging; 9 | using Microsoft.OpenApi.Models; 10 | using System; 11 | using System.Collections.Generic; 12 | using System.Linq; 13 | using System.Threading.Tasks; 14 | 15 | namespace TravelAgent.HospitalApi 16 | { 17 | public class Startup 18 | { 19 | public Startup(IConfiguration configuration) 20 | { 21 | Configuration = configuration; 22 | } 23 | 24 | public IConfiguration Configuration { get; } 25 | 26 | // This method gets called by the runtime. Use this method to add services to the container. 27 | public void ConfigureServices(IServiceCollection services) 28 | { 29 | 30 | services.AddControllers(); 31 | services.AddSwaggerGen(c => 32 | { 33 | c.SwaggerDoc("v1", new OpenApiInfo { Title = "TravelAgent.HospitalApi", Version = "v1" }); 34 | }); 35 | } 36 | 37 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 38 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 39 | { 40 | if (env.IsDevelopment()) 41 | { 42 | app.UseDeveloperExceptionPage(); 43 | app.UseSwagger(); 44 | app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "TravelAgent.HospitalApi v1")); 45 | } 46 | 47 | app.UseHttpsRedirection(); 48 | 49 | app.UseRouting(); 50 | 51 | app.UseAuthorization(); 52 | 53 | app.UseEndpoints(endpoints => 54 | { 55 | endpoints.MapControllers(); 56 | }); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /chapter-3-travel-agent/src/TravelAgent/TravelAgent/TravelAgent.HospitalApi/TravelAgent.HospitalApi.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /chapter-3-travel-agent/src/TravelAgent/TravelAgent/TravelAgent.HospitalApi/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /chapter-3-travel-agent/src/TravelAgent/TravelAgent/TravelAgent.HospitalApi/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "AllowedHosts": "*" 10 | } 11 | -------------------------------------------------------------------------------- /chapter-3-travel-agent/src/TravelAgent/TravelAgent/TravelAgent.HotelApi/Controllers/AvailabilityController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Microsoft.Extensions.Logging; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | using TravelAgent.HotelApi.Models; 8 | 9 | namespace TravelAgent.HotelApi.Controllers 10 | { 11 | [ApiController] 12 | [Route("[controller]")] 13 | public class AvailabilityController : ControllerBase 14 | { 15 | private static Random _rnd = new Random(); 16 | private readonly ILogger _logger; 17 | 18 | public AvailabilityController(ILogger logger) 19 | { 20 | _logger = logger; 21 | } 22 | 23 | [HttpGet] 24 | public IActionResult Get(DateTime date) 25 | { 26 | var response = new AvailabilityResponse() 27 | { 28 | Price = _rnd.Next(1) == 0 ? 120 : 150, 29 | RoomCount = _rnd.Next(3) 30 | }; 31 | 32 | return Ok(response); 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /chapter-3-travel-agent/src/TravelAgent/TravelAgent/TravelAgent.HotelApi/Controllers/BookingController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Microsoft.Extensions.Logging; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | using TravelAgent.HotelApi.Models; 8 | 9 | namespace TravelAgent.HotelApi.Controllers 10 | { 11 | [ApiController] 12 | [Route("[controller]")] 13 | public class BookingController : ControllerBase 14 | { 15 | private static Random _rnd = new Random(); 16 | private readonly ILogger _logger; 17 | 18 | public BookingController(ILogger logger) 19 | { 20 | _logger = logger; 21 | } 22 | 23 | [HttpPost] 24 | [Route("/book")] 25 | public async Task BookRoom(RoomRequest roomRequest) 26 | { 27 | await Task.Delay(_rnd.Next(5000)); // Simulate slow response 28 | 29 | if (_rnd.Next(30) == 1) 30 | { 31 | return NotFound("No available rooms"); 32 | } 33 | return Ok(); 34 | } 35 | 36 | [HttpPost] 37 | [Route("/cancel")] 38 | public async Task CancelRoom(RoomRequest roomRequest) 39 | { 40 | await Task.Delay(_rnd.Next(5000)); // Simulate slow response 41 | 42 | if (_rnd.Next(30) == 1) 43 | { 44 | return BadRequest("Cancellation refused"); 45 | } 46 | return Ok(); 47 | } 48 | 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /chapter-3-travel-agent/src/TravelAgent/TravelAgent/TravelAgent.HotelApi/Models/AvailabilityResponse.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace TravelAgent.HotelApi.Models 4 | { 5 | public class AvailabilityResponse 6 | { 7 | public decimal Price { get; set; } 8 | public int RoomCount { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /chapter-3-travel-agent/src/TravelAgent/TravelAgent/TravelAgent.HotelApi/Models/RoomRequest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace TravelAgent.HotelApi.Models 4 | { 5 | public class RoomRequest 6 | { 7 | public DateTime Date { get; set; } 8 | public int RoomType { get; set; } 9 | 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /chapter-3-travel-agent/src/TravelAgent/TravelAgent/TravelAgent.HotelApi/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.Extensions.Configuration; 3 | using Microsoft.Extensions.Hosting; 4 | using Microsoft.Extensions.Logging; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Threading.Tasks; 9 | 10 | namespace TravelAgent.HotelApi 11 | { 12 | public class Program 13 | { 14 | public static void Main(string[] args) 15 | { 16 | CreateHostBuilder(args).Build().Run(); 17 | } 18 | 19 | public static IHostBuilder CreateHostBuilder(string[] args) => 20 | Host.CreateDefaultBuilder(args) 21 | .ConfigureWebHostDefaults(webBuilder => 22 | { 23 | webBuilder.UseStartup(); 24 | }); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /chapter-3-travel-agent/src/TravelAgent/TravelAgent/TravelAgent.HotelApi/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:62989", 7 | "sslPort": 44375 8 | } 9 | }, 10 | "$schema": "http://json.schemastore.org/launchsettings.json", 11 | "profiles": { 12 | "IIS Express": { 13 | "commandName": "IISExpress", 14 | "launchBrowser": true, 15 | "launchUrl": "swagger", 16 | "environmentVariables": { 17 | "ASPNETCORE_ENVIRONMENT": "Development" 18 | } 19 | }, 20 | "TravelAgent.HotelApi": { 21 | "commandName": "Project", 22 | "launchBrowser": true, 23 | "launchUrl": "swagger", 24 | "environmentVariables": { 25 | "ASPNETCORE_ENVIRONMENT": "Development" 26 | }, 27 | "dotnetRunMessages": "true", 28 | "applicationUrl": "https://localhost:5020;http://localhost:5123" 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /chapter-3-travel-agent/src/TravelAgent/TravelAgent/TravelAgent.HotelApi/Startup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.AspNetCore.Hosting; 3 | using Microsoft.AspNetCore.Http; 4 | using Microsoft.AspNetCore.HttpsPolicy; 5 | using Microsoft.AspNetCore.Mvc; 6 | using Microsoft.Extensions.Configuration; 7 | using Microsoft.Extensions.DependencyInjection; 8 | using Microsoft.Extensions.Hosting; 9 | using Microsoft.Extensions.Logging; 10 | using Microsoft.OpenApi.Models; 11 | using System; 12 | using System.Collections.Generic; 13 | using System.Linq; 14 | using System.Threading.Tasks; 15 | 16 | namespace TravelAgent.HotelApi 17 | { 18 | public class Startup 19 | { 20 | public Startup(IConfiguration configuration) 21 | { 22 | Configuration = configuration; 23 | } 24 | 25 | public IConfiguration Configuration { get; } 26 | 27 | // This method gets called by the runtime. Use this method to add services to the container. 28 | public void ConfigureServices(IServiceCollection services) 29 | { 30 | 31 | services.AddControllers(); 32 | services.AddSwaggerGen(c => 33 | { 34 | c.SwaggerDoc("v1", new OpenApiInfo { Title = "TravelAgent.HotelApi", Version = "v1" }); 35 | }); 36 | } 37 | 38 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 39 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 40 | { 41 | if (env.IsDevelopment()) 42 | { 43 | app.UseDeveloperExceptionPage(); 44 | app.UseSwagger(); 45 | app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "TravelAgent.HotelApi v1")); 46 | } 47 | 48 | app.UseHttpsRedirection(); 49 | 50 | app.UseRouting(); 51 | 52 | app.Use(next => context => 53 | { 54 | Console.WriteLine($"Found: {context.GetEndpoint()?.DisplayName}"); 55 | return next(context); 56 | }); 57 | 58 | app.UseAuthorization(); 59 | 60 | app.UseEndpoints(endpoints => 61 | { 62 | endpoints.MapControllers(); 63 | }); 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /chapter-3-travel-agent/src/TravelAgent/TravelAgent/TravelAgent.HotelApi/TravelAgent.HotelApi.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /chapter-3-travel-agent/src/TravelAgent/TravelAgent/TravelAgent.HotelApi/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /chapter-3-travel-agent/src/TravelAgent/TravelAgent/TravelAgent.HotelApi/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "AllowedHosts": "*" 10 | } 11 | -------------------------------------------------------------------------------- /chapter-3-travel-agent/src/TravelAgent/TravelAgent/TravelAgent.SpaceTripApi/Controllers/SpaceFlightBookingController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Microsoft.Extensions.Logging; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Threading.Tasks; 7 | using TravelAgent.SpaceTripApi.Models; 8 | 9 | namespace TravelAgent.SpaceTripApi.Controllers 10 | { 11 | [ApiController] 12 | [Route("[controller]")] 13 | public class SpaceFlightBookingController : ControllerBase 14 | { 15 | private static Random _rnd = new Random(); 16 | private readonly ILogger _logger; 17 | 18 | public SpaceFlightBookingController(ILogger logger) 19 | { 20 | _logger = logger; 21 | } 22 | 23 | [HttpPost] 24 | [Route("/book")] 25 | public async Task BookFlight(BookingRequest bookingRequest) 26 | { 27 | await Task.Delay(_rnd.Next(5000)); // Simulate slow response 28 | 29 | if (_rnd.Next(200) == 1) 30 | { 31 | return NotFound("Unable to book space flight"); 32 | } 33 | return Ok(); 34 | } 35 | 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /chapter-3-travel-agent/src/TravelAgent/TravelAgent/TravelAgent.SpaceTripApi/Models/BookingRequest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Threading.Tasks; 5 | 6 | namespace TravelAgent.SpaceTripApi.Models 7 | { 8 | public class BookingRequest 9 | { 10 | public DateTime FlightDate { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /chapter-3-travel-agent/src/TravelAgent/TravelAgent/TravelAgent.SpaceTripApi/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.Extensions.Configuration; 3 | using Microsoft.Extensions.Hosting; 4 | using Microsoft.Extensions.Logging; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Threading.Tasks; 9 | 10 | namespace TravelAgent.SpaceTripApi 11 | { 12 | public class Program 13 | { 14 | public static void Main(string[] args) 15 | { 16 | CreateHostBuilder(args).Build().Run(); 17 | } 18 | 19 | public static IHostBuilder CreateHostBuilder(string[] args) => 20 | Host.CreateDefaultBuilder(args) 21 | .ConfigureWebHostDefaults(webBuilder => 22 | { 23 | webBuilder.UseStartup(); 24 | }); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /chapter-3-travel-agent/src/TravelAgent/TravelAgent/TravelAgent.SpaceTripApi/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:62429", 7 | "sslPort": 44327 8 | } 9 | }, 10 | "$schema": "http://json.schemastore.org/launchsettings.json", 11 | "profiles": { 12 | "IIS Express": { 13 | "commandName": "IISExpress", 14 | "launchBrowser": true, 15 | "launchUrl": "swagger", 16 | "environmentVariables": { 17 | "ASPNETCORE_ENVIRONMENT": "Development" 18 | } 19 | }, 20 | "TravelAgent.SpaceTripApi": { 21 | "commandName": "Project", 22 | "launchBrowser": true, 23 | "launchUrl": "swagger", 24 | "environmentVariables": { 25 | "ASPNETCORE_ENVIRONMENT": "Development" 26 | }, 27 | "dotnetRunMessages": "true", 28 | "applicationUrl": "https://localhost:5101;http://localhost:5002" 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /chapter-3-travel-agent/src/TravelAgent/TravelAgent/TravelAgent.SpaceTripApi/Startup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.AspNetCore.Hosting; 3 | using Microsoft.AspNetCore.HttpsPolicy; 4 | using Microsoft.AspNetCore.Mvc; 5 | using Microsoft.Extensions.Configuration; 6 | using Microsoft.Extensions.DependencyInjection; 7 | using Microsoft.Extensions.Hosting; 8 | using Microsoft.Extensions.Logging; 9 | using Microsoft.OpenApi.Models; 10 | using System; 11 | using System.Collections.Generic; 12 | using System.Linq; 13 | using System.Threading.Tasks; 14 | 15 | namespace TravelAgent.SpaceTripApi 16 | { 17 | public class Startup 18 | { 19 | public Startup(IConfiguration configuration) 20 | { 21 | Configuration = configuration; 22 | } 23 | 24 | public IConfiguration Configuration { get; } 25 | 26 | // This method gets called by the runtime. Use this method to add services to the container. 27 | public void ConfigureServices(IServiceCollection services) 28 | { 29 | 30 | services.AddControllers(); 31 | services.AddSwaggerGen(c => 32 | { 33 | c.SwaggerDoc("v1", new OpenApiInfo { Title = "TravelAgent.SpaceTripApi", Version = "v1" }); 34 | }); 35 | } 36 | 37 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 38 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 39 | { 40 | if (env.IsDevelopment()) 41 | { 42 | app.UseDeveloperExceptionPage(); 43 | app.UseSwagger(); 44 | app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "TravelAgent.SpaceTripApi v1")); 45 | } 46 | 47 | app.UseHttpsRedirection(); 48 | 49 | app.UseRouting(); 50 | 51 | app.UseAuthorization(); 52 | 53 | app.UseEndpoints(endpoints => 54 | { 55 | endpoints.MapControllers(); 56 | }); 57 | } 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /chapter-3-travel-agent/src/TravelAgent/TravelAgent/TravelAgent.SpaceTripApi/TravelAgent.SpaceTripApi.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /chapter-3-travel-agent/src/TravelAgent/TravelAgent/TravelAgent.SpaceTripApi/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /chapter-3-travel-agent/src/TravelAgent/TravelAgent/TravelAgent.SpaceTripApi/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "AllowedHosts": "*" 10 | } 11 | -------------------------------------------------------------------------------- /chapter-3-travel-agent/src/TravelAgent/TravelAgent/TravelAgent.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30803.129 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TravelAgent.Coordinator", "TravelAgent.Coordinator\TravelAgent.Coordinator.csproj", "{B624A450-EFF8-4916-BF92-7ED305597198}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TravelAgent.HotelApi", "TravelAgent.HotelApi\TravelAgent.HotelApi.csproj", "{E42822F8-DCEB-42EE-A67A-74FE9308AD91}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TravelAgent.HospitalApi", "TravelAgent.HospitalApi\TravelAgent.HospitalApi.csproj", "{A4E040B8-6A56-4A66-8FE5-9376E3F53628}" 11 | EndProject 12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ThirdPartyApis", "ThirdPartyApis", "{2298768C-8CE2-438E-909E-1B87C61B4170}" 13 | EndProject 14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TravelAgent.ApiTests", "..\..\..\tests\TravelAgent.ApiTests\TravelAgent.ApiTests.csproj", "{FB770ED7-12EA-462F-9EA9-59FC88749DCC}" 15 | EndProject 16 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TravelAgent.SpaceTripApi", "TravelAgent.SpaceTripApi\TravelAgent.SpaceTripApi.csproj", "{1CA319B3-3052-4113-B8ED-C7ACEB77332B}" 17 | EndProject 18 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TravelAgent.Client", "TravelAgent.Client\TravelAgent.Client.csproj", "{4840AE3B-62B7-4A6D-BC48-EC85C065A8BA}" 19 | EndProject 20 | Global 21 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 22 | Debug|Any CPU = Debug|Any CPU 23 | Release|Any CPU = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 26 | {B624A450-EFF8-4916-BF92-7ED305597198}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {B624A450-EFF8-4916-BF92-7ED305597198}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {B624A450-EFF8-4916-BF92-7ED305597198}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {B624A450-EFF8-4916-BF92-7ED305597198}.Release|Any CPU.Build.0 = Release|Any CPU 30 | {E42822F8-DCEB-42EE-A67A-74FE9308AD91}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {E42822F8-DCEB-42EE-A67A-74FE9308AD91}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {E42822F8-DCEB-42EE-A67A-74FE9308AD91}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {E42822F8-DCEB-42EE-A67A-74FE9308AD91}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {A4E040B8-6A56-4A66-8FE5-9376E3F53628}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {A4E040B8-6A56-4A66-8FE5-9376E3F53628}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {A4E040B8-6A56-4A66-8FE5-9376E3F53628}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {A4E040B8-6A56-4A66-8FE5-9376E3F53628}.Release|Any CPU.Build.0 = Release|Any CPU 38 | {FB770ED7-12EA-462F-9EA9-59FC88749DCC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {FB770ED7-12EA-462F-9EA9-59FC88749DCC}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {FB770ED7-12EA-462F-9EA9-59FC88749DCC}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {FB770ED7-12EA-462F-9EA9-59FC88749DCC}.Release|Any CPU.Build.0 = Release|Any CPU 42 | {1CA319B3-3052-4113-B8ED-C7ACEB77332B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 43 | {1CA319B3-3052-4113-B8ED-C7ACEB77332B}.Debug|Any CPU.Build.0 = Debug|Any CPU 44 | {1CA319B3-3052-4113-B8ED-C7ACEB77332B}.Release|Any CPU.ActiveCfg = Release|Any CPU 45 | {1CA319B3-3052-4113-B8ED-C7ACEB77332B}.Release|Any CPU.Build.0 = Release|Any CPU 46 | {4840AE3B-62B7-4A6D-BC48-EC85C065A8BA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 47 | {4840AE3B-62B7-4A6D-BC48-EC85C065A8BA}.Debug|Any CPU.Build.0 = Debug|Any CPU 48 | {4840AE3B-62B7-4A6D-BC48-EC85C065A8BA}.Release|Any CPU.ActiveCfg = Release|Any CPU 49 | {4840AE3B-62B7-4A6D-BC48-EC85C065A8BA}.Release|Any CPU.Build.0 = Release|Any CPU 50 | EndGlobalSection 51 | GlobalSection(SolutionProperties) = preSolution 52 | HideSolutionNode = FALSE 53 | EndGlobalSection 54 | GlobalSection(NestedProjects) = preSolution 55 | {E42822F8-DCEB-42EE-A67A-74FE9308AD91} = {2298768C-8CE2-438E-909E-1B87C61B4170} 56 | {A4E040B8-6A56-4A66-8FE5-9376E3F53628} = {2298768C-8CE2-438E-909E-1B87C61B4170} 57 | {FB770ED7-12EA-462F-9EA9-59FC88749DCC} = {2298768C-8CE2-438E-909E-1B87C61B4170} 58 | {1CA319B3-3052-4113-B8ED-C7ACEB77332B} = {2298768C-8CE2-438E-909E-1B87C61B4170} 59 | EndGlobalSection 60 | GlobalSection(ExtensibilityGlobals) = postSolution 61 | SolutionGuid = {FDFB8F05-C475-4E25-A5B7-85F737E83C86} 62 | EndGlobalSection 63 | EndGlobal 64 | -------------------------------------------------------------------------------- /chapter-3-travel-agent/tests/TravelAgent.ApiTests/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "iisSettings": { 3 | "windowsAuthentication": false, 4 | "anonymousAuthentication": true, 5 | "iisExpress": { 6 | "applicationUrl": "http://localhost:49387/", 7 | "sslPort": 44368 8 | } 9 | }, 10 | "profiles": { 11 | "IIS Express": { 12 | "commandName": "IISExpress", 13 | "launchBrowser": true, 14 | "environmentVariables": { 15 | "ASPNETCORE_ENVIRONMENT": "Development" 16 | } 17 | }, 18 | "TravelAgent.ApiTests": { 19 | "commandName": "Project", 20 | "launchBrowser": true, 21 | "environmentVariables": { 22 | "ASPNETCORE_ENVIRONMENT": "Development" 23 | }, 24 | "applicationUrl": "https://localhost:5001;http://localhost:5000" 25 | } 26 | } 27 | } -------------------------------------------------------------------------------- /chapter-3-travel-agent/tests/TravelAgent.ApiTests/TestHotelApi.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.AspNetCore.Mvc.Testing; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Net.Http; 7 | using System.Text; 8 | using System.Threading.Tasks; 9 | using TravelAgent.HotelApi; 10 | using Xunit; 11 | 12 | namespace TravelAgent.ApiTests 13 | { 14 | public class TestHotelApi 15 | { 16 | [Fact] 17 | public async Task HotelApi_GetAvailability_Returns200OK() 18 | { 19 | // Arrange 20 | using var server = new WebApplicationFactory(); 21 | using var client = server.CreateClient(); 22 | 23 | var dateTime = new DateTime(2021, 01, 02); 24 | string dateParam = dateTime.ToString("yyyy-MM-dd"); 25 | 26 | // Act 27 | var result = await client.GetAsync($"https://localhost:44375/Availability?date={dateParam}"); 28 | 29 | // Assert 30 | Assert.True(result.IsSuccessStatusCode); 31 | } 32 | 33 | [Fact] 34 | public async Task HotelApi_BookRoom_Returns200OK() 35 | { 36 | // Arrange 37 | using var server = new WebApplicationFactory(); 38 | using var client = server.CreateClient(); 39 | 40 | var date = new DateTime(2021, 01, 02); 41 | 42 | string roomRequestJson = "{ \"date\": \"" + date.ToString("yyyy-MM-ddTHH:mm:ssZ") + "\", \"roomType\": 1 }"; 43 | 44 | var requestContent = new StringContent( 45 | roomRequestJson, 46 | Encoding.UTF8, 47 | "application/json"); 48 | 49 | // Act 50 | var result = await client.PostAsync("https://localhost:44375/book", requestContent); 51 | 52 | // Assert 53 | Assert.True(result.IsSuccessStatusCode); 54 | } 55 | 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /chapter-3-travel-agent/tests/TravelAgent.ApiTests/TravelAgent.ApiTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net5.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | all 13 | runtime; build; native; contentfiles; analyzers; buildtransitive 14 | 15 | 16 | all 17 | runtime; build; native; contentfiles; analyzers; buildtransitive 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | -------------------------------------------------------------------------------- /chapter-4-social-media/src/SocialMedia/SocialMedia.Client/Helpers/Extensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace SocialMedia.Client.Helpers 7 | { 8 | public static class Extensions 9 | { 10 | private static Random _random = new Random(); 11 | 12 | public static T GetRandom(this List list) 13 | { 14 | int idx = _random.Next(list.Count); 15 | return list.ElementAt(idx); 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /chapter-4-social-media/src/SocialMedia/SocialMedia.Client/Program.cs: -------------------------------------------------------------------------------- 1 | using SocialMedia.Client.Helpers; 2 | using SocialMedia.Data.Mongo; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Diagnostics; 6 | using System.Net.Http; 7 | using System.Threading.Tasks; 8 | 9 | namespace SocialMedia.Client 10 | { 11 | class Program 12 | { 13 | static readonly List _posts = new List(); 14 | 15 | static async Task Main(string[] args) 16 | { 17 | while (true) 18 | { 19 | Console.WriteLine("Choose action"); 20 | Console.WriteLine("1: Create Post"); 21 | Console.WriteLine("2: Comment on Post"); 22 | Console.WriteLine("3: Create Post and Comment"); 23 | Console.WriteLine("4: Small Bulk Test"); 24 | Console.WriteLine("5: Large Bulk Test"); 25 | 26 | Console.WriteLine("0: Exit"); 27 | 28 | var result = Console.ReadKey(); 29 | switch (result.Key) 30 | { 31 | case ConsoleKey.D1: 32 | await CreatePost(); 33 | break; 34 | 35 | case ConsoleKey.D2: 36 | await CreateComment(); 37 | break; 38 | 39 | case ConsoleKey.D3: 40 | var postId = await CreateSinglePost(); 41 | await CreateComment(postId); 42 | break; 43 | 44 | case ConsoleKey.D4: 45 | for (int i = 1; i <= 100; i++) 46 | { 47 | await CreatePost(); 48 | await CreateComment(); 49 | } 50 | break; 51 | 52 | case ConsoleKey.D5: 53 | for (int i = 1; i <= 100; i++) 54 | { 55 | Console.WriteLine($"Processing batch {i}"); 56 | for (int j = 1; j <= 100; j++) 57 | { 58 | await CreatePost(); 59 | await CreateComment(); 60 | } 61 | await Task.Delay(20); 62 | } 63 | break; 64 | 65 | case ConsoleKey.D0: 66 | return; 67 | } 68 | } 69 | } 70 | 71 | private static async Task CreateComment(string postId = null) 72 | { 73 | if (postId == null) postId = _posts.GetRandom(); 74 | 75 | var httpClient = HttpClientFactory.Create(); 76 | 77 | var httpContent = new StringContent(postId); 78 | var result = await httpClient.PostAsync("https://localhost:44388/Comment", httpContent); 79 | 80 | Debug.Assert(result.IsSuccessStatusCode); 81 | } 82 | 83 | private static async Task CreateSinglePost() 84 | { 85 | var httpClient = HttpClientFactory.Create(); 86 | 87 | var httpContent = new StringContent(""); 88 | var result = await httpClient.PostAsync("https://localhost:44388/Post", httpContent); 89 | 90 | Debug.Assert(result.IsSuccessStatusCode); 91 | 92 | return result.Content.ToString(); 93 | } 94 | 95 | private static async Task CreatePost() 96 | { 97 | var createPostResult = await CreateSinglePost(); 98 | _posts.Add(createPostResult); 99 | } 100 | } 101 | } -------------------------------------------------------------------------------- /chapter-4-social-media/src/SocialMedia/SocialMedia.Client/SocialMedia.Client.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net5.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /chapter-4-social-media/src/SocialMedia/SocialMedia.Data.Mongo/Entities/Comment.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace SocialMedia.Data.Mongo.Entities 6 | { 7 | public class Comment : EntityBase 8 | { 9 | public string PostId { get; set; } 10 | public string Text { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /chapter-4-social-media/src/SocialMedia/SocialMedia.Data.Mongo/Entities/EntityBase.cs: -------------------------------------------------------------------------------- 1 | using MongoDB.Bson; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | 6 | namespace SocialMedia.Data.Mongo.Entities 7 | { 8 | public class EntityBase 9 | { 10 | public ObjectId Id { get; set; } 11 | public DateTimeOffset CreatedAt { get; set; } 12 | public DateTimeOffset UpdatedAt { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /chapter-4-social-media/src/SocialMedia/SocialMedia.Data.Mongo/Entities/Post.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace SocialMedia.Data.Mongo.Entities 6 | { 7 | public class Post : EntityBase 8 | { 9 | public DateTimeOffset WorkoutDate { get; set; } 10 | public string Text { get; set; } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /chapter-4-social-media/src/SocialMedia/SocialMedia.Data.Mongo/IMongoDBWrapper.cs: -------------------------------------------------------------------------------- 1 | using SocialMedia.Data.Mongo.Entities; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace SocialMedia.Data.Mongo 8 | { 9 | public interface IMongoDBWrapper 10 | { 11 | Task CreatePost(DateTime workoutDate, string comment); 12 | Task CreateComment(string comment, string postId); 13 | Task GetNextPost(); 14 | Task GetNextComment(string postId); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /chapter-4-social-media/src/SocialMedia/SocialMedia.Data.Mongo/MongoDBWrapper.cs: -------------------------------------------------------------------------------- 1 | using MongoDB.Bson; 2 | using MongoDB.Bson.Serialization; 3 | using MongoDB.Driver; 4 | using SocialMedia.Data.Mongo.Entities; 5 | using System; 6 | using System.Threading.Tasks; 7 | 8 | namespace SocialMedia.Data.Mongo 9 | { 10 | public class MongoDBWrapper : IMongoDBWrapper 11 | { 12 | readonly IMongoDatabase _db; 13 | 14 | public MongoDBWrapper() 15 | { 16 | var dbClient = new MongoClient("mongodb://localhost:27017"); 17 | _db = dbClient.GetDatabase("SocialMedia"); 18 | } 19 | 20 | public async Task CreateComment(string comment, string postId) 21 | { 22 | var newComment = new Comment() 23 | { 24 | PostId = postId, 25 | Text = comment 26 | }; 27 | 28 | var collection = _db.GetCollection("Comments"); 29 | await collection.InsertOneAsync(newComment); 30 | 31 | return newComment.Id.ToString(); 32 | } 33 | 34 | public async Task CreatePost(DateTime workoutDate, string comment) 35 | { 36 | var newPost = new Post() 37 | { 38 | WorkoutDate = workoutDate, 39 | Text = comment 40 | }; 41 | 42 | var collection = _db.GetCollection("Posts"); 43 | await collection.InsertOneAsync(newPost); 44 | 45 | return newPost.Id.ToString(); 46 | } 47 | 48 | public async Task GetNextPost() 49 | { 50 | var collection = _db.GetCollection("Posts"); 51 | var result = await collection.FindOneAndDeleteAsync(a => true); 52 | return result; 53 | } 54 | 55 | public async Task GetNextComment(string postId) 56 | { 57 | var collection = _db.GetCollection("Comments"); 58 | var result = await collection.FindOneAndDeleteAsync(a => a.PostId == postId); 59 | return result; 60 | } 61 | } 62 | } 63 | 64 | -------------------------------------------------------------------------------- /chapter-4-social-media/src/SocialMedia/SocialMedia.Data.Mongo/SocialMedia.Data.Mongo.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp3.1 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /chapter-4-social-media/src/SocialMedia/SocialMedia.Transfer/Program.cs: -------------------------------------------------------------------------------- 1 | using Dapper; 2 | using SocialMedia.Data.Mongo; 3 | using SocialMedia.Data.Mongo.Entities; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Data; 7 | using System.Data.SqlClient; 8 | using System.Linq; 9 | using System.Runtime.CompilerServices; 10 | using System.Threading.Tasks; 11 | 12 | namespace SocialMedia.Transfer 13 | { 14 | class Program 15 | { 16 | static async Task Main(string[] args) 17 | { 18 | using var connection = new SqlConnection(@"Data Source=.\SQLEXPRESS;Initial Catalog=SocialMedia;Integrated Security=True;"); 19 | connection.Open(); 20 | 21 | await ReadPosts(connection, new MongoDBWrapper()); 22 | } 23 | 24 | private static async Task ReadPosts(SqlConnection connection, MongoDBWrapper wrapper) 25 | { 26 | while (true) 27 | { 28 | // Read from Mongo 29 | var nextPost = await wrapper.GetNextPost(); 30 | if (nextPost == null) break; 31 | 32 | Console.WriteLine($"ReadPosts: Read {nextPost.Id}"); 33 | 34 | using var transaction = connection.BeginTransaction(); 35 | 36 | // Write To Sql Server 37 | string sql = "DECLARE @newRecord table(newId uniqueidentifier); " 38 | + "INSERT INTO Post " 39 | + "(Text, WorkoutDate) " 40 | + "OUTPUT INSERTED.Id INTO @newRecord " 41 | + "VALUES " 42 | + "(@text, @workoutDate) " 43 | + "SELECT CONVERT(nvarchar(50), newId) FROM @newRecord"; 44 | var result = await connection.QueryAsync(sql, 45 | new { text = nextPost.Text, workoutDate = nextPost.WorkoutDate }, 46 | transaction); 47 | 48 | // Get all comments for post 49 | await ReadComments(transaction, connection, 50 | result.Single(), nextPost.Id.ToString(), wrapper); 51 | 52 | transaction.Commit(); 53 | } 54 | 55 | Console.WriteLine("ReadPosts: End"); 56 | } 57 | 58 | private static async Task ReadComments(SqlTransaction transaction, SqlConnection connection, 59 | string postId, string filterPostId, IMongoDBWrapper wrapper) 60 | { 61 | while (true) 62 | { 63 | // Read from Mongo 64 | var nextComment = await wrapper.GetNextComment(filterPostId); 65 | if (nextComment == null) break; 66 | 67 | // Write To Sql Server 68 | string sql = "INSERT INTO Comment " 69 | + "(Text, PostId) " 70 | + "VALUES " 71 | + "(@text, @postId)"; 72 | 73 | var result = await connection.ExecuteAsync(sql, 74 | new { text = nextComment.Text, postId = postId }, 75 | transaction); 76 | } 77 | } 78 | 79 | } 80 | } 81 | -------------------------------------------------------------------------------- /chapter-4-social-media/src/SocialMedia/SocialMedia.Transfer/SocialMedia.ProcessDataService.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net5.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /chapter-4-social-media/src/SocialMedia/SocialMedia.UpdateService/Controllers/CommentController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Microsoft.Extensions.Logging; 3 | using SocialMedia.Data.Mongo; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Threading.Tasks; 8 | 9 | namespace SocialMedia.UpdateService.Controllers 10 | { 11 | [ApiController] 12 | [Route("[controller]")] 13 | public class CommentController : ControllerBase 14 | { 15 | private readonly IMongoDBWrapper _mongoDBWrapper; 16 | 17 | public CommentController(IMongoDBWrapper mongoDBWrapper) 18 | { 19 | _mongoDBWrapper = mongoDBWrapper; 20 | } 21 | 22 | [HttpPost] 23 | public async Task Create(string postId) => 24 | await _mongoDBWrapper.CreateComment($"test comment {DateTime.Now}", postId); 25 | 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /chapter-4-social-media/src/SocialMedia/SocialMedia.UpdateService/Controllers/PostController.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | using Microsoft.Extensions.Logging; 3 | using SocialMedia.Data.Mongo; 4 | using System; 5 | using System.Collections.Generic; 6 | using System.Linq; 7 | using System.Threading.Tasks; 8 | 9 | namespace SocialMedia.UpdateService.Controllers 10 | { 11 | [ApiController] 12 | [Route("[controller]")] 13 | public class PostController : ControllerBase 14 | { 15 | private readonly IMongoDBWrapper _mongoDBWrapper; 16 | 17 | public PostController(IMongoDBWrapper mongoDBWrapper) 18 | { 19 | _mongoDBWrapper = mongoDBWrapper; 20 | } 21 | 22 | [HttpPost] 23 | public async Task Create() => 24 | await _mongoDBWrapper.CreatePost(DateTime.Now, $"test post {DateTime.Now}"); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /chapter-4-social-media/src/SocialMedia/SocialMedia.UpdateService/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Hosting; 2 | using Microsoft.Extensions.Configuration; 3 | using Microsoft.Extensions.Hosting; 4 | using Microsoft.Extensions.Logging; 5 | using System; 6 | using System.Collections.Generic; 7 | using System.Linq; 8 | using System.Threading.Tasks; 9 | 10 | namespace SocialMedia.UpdateService 11 | { 12 | public class Program 13 | { 14 | public static void Main(string[] args) 15 | { 16 | CreateHostBuilder(args).Build().Run(); 17 | } 18 | 19 | public static IHostBuilder CreateHostBuilder(string[] args) => 20 | Host.CreateDefaultBuilder(args) 21 | .ConfigureWebHostDefaults(webBuilder => 22 | { 23 | webBuilder.UseStartup(); 24 | }); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /chapter-4-social-media/src/SocialMedia/SocialMedia.UpdateService/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "http://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:58617", 8 | "sslPort": 44388 9 | } 10 | }, 11 | "profiles": { 12 | "IIS Express": { 13 | "commandName": "IISExpress", 14 | "launchBrowser": true, 15 | "launchUrl": "swagger", 16 | "environmentVariables": { 17 | "ASPNETCORE_ENVIRONMENT": "Development" 18 | } 19 | }, 20 | "SocialMedia.UpdateService": { 21 | "commandName": "Project", 22 | "dotnetRunMessages": "true", 23 | "launchBrowser": true, 24 | "launchUrl": "swagger", 25 | "applicationUrl": "https://localhost:5001;http://localhost:5000", 26 | "environmentVariables": { 27 | "ASPNETCORE_ENVIRONMENT": "Development" 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /chapter-4-social-media/src/SocialMedia/SocialMedia.UpdateService/SocialMedia.UpdateService.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | -------------------------------------------------------------------------------- /chapter-4-social-media/src/SocialMedia/SocialMedia.UpdateService/Startup.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Builder; 2 | using Microsoft.AspNetCore.Hosting; 3 | using Microsoft.AspNetCore.HttpsPolicy; 4 | using Microsoft.AspNetCore.Mvc; 5 | using Microsoft.Extensions.Configuration; 6 | using Microsoft.Extensions.DependencyInjection; 7 | using Microsoft.Extensions.Hosting; 8 | using Microsoft.Extensions.Logging; 9 | using Microsoft.OpenApi.Models; 10 | using SocialMedia.Data.Mongo; 11 | using System; 12 | using System.Collections.Generic; 13 | using System.Linq; 14 | using System.Threading.Tasks; 15 | 16 | namespace SocialMedia.UpdateService 17 | { 18 | public class Startup 19 | { 20 | public Startup(IConfiguration configuration) 21 | { 22 | Configuration = configuration; 23 | } 24 | 25 | public IConfiguration Configuration { get; } 26 | 27 | // This method gets called by the runtime. Use this method to add services to the container. 28 | public void ConfigureServices(IServiceCollection services) 29 | { 30 | services.AddControllers(); 31 | services.AddSwaggerGen(c => 32 | { 33 | c.SwaggerDoc("v1", new OpenApiInfo { Title = "SocialMedia.UpdateService", Version = "v1" }); 34 | }); 35 | 36 | services.AddSingleton(); 37 | } 38 | 39 | // This method gets called by the runtime. Use this method to configure the HTTP request pipeline. 40 | public void Configure(IApplicationBuilder app, IWebHostEnvironment env) 41 | { 42 | if (env.IsDevelopment()) 43 | { 44 | app.UseDeveloperExceptionPage(); 45 | app.UseSwagger(); 46 | app.UseSwaggerUI(c => c.SwaggerEndpoint("/swagger/v1/swagger.json", "SocialMedia.UpdateService v1")); 47 | } 48 | 49 | app.UseHttpsRedirection(); 50 | 51 | app.UseRouting(); 52 | 53 | app.UseAuthorization(); 54 | 55 | app.UseEndpoints(endpoints => 56 | { 57 | endpoints.MapControllers(); 58 | }); 59 | } 60 | } 61 | } 62 | -------------------------------------------------------------------------------- /chapter-4-social-media/src/SocialMedia/SocialMedia.UpdateService/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /chapter-4-social-media/src/SocialMedia/SocialMedia.UpdateService/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft": "Warning", 6 | "Microsoft.Hosting.Lifetime": "Information" 7 | } 8 | }, 9 | "AllowedHosts": "*" 10 | } 11 | -------------------------------------------------------------------------------- /chapter-4-social-media/src/SocialMedia/SocialMedia.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.30803.129 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SocialMedia.Client", "SocialMedia.Client\SocialMedia.Client.csproj", "{D8F25263-F4BE-474A-85A5-65EC63AA7BB5}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SocialMedia.ProcessDataService", "SocialMedia.Transfer\SocialMedia.ProcessDataService.csproj", "{43B3320B-CB65-4701-A37B-9650356FD8F6}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SocialMedia.Data.Mongo", "SocialMedia.Data.Mongo\SocialMedia.Data.Mongo.csproj", "{FE8F3381-C4CA-4290-865B-D717506A1E8A}" 11 | EndProject 12 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SocialMedia.UpdateService", "SocialMedia.UpdateService\SocialMedia.UpdateService.csproj", "{1379E7F6-F83C-493C-B0F4-2B194EC68932}" 13 | EndProject 14 | Global 15 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 16 | Debug|Any CPU = Debug|Any CPU 17 | Release|Any CPU = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 20 | {D8F25263-F4BE-474A-85A5-65EC63AA7BB5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {D8F25263-F4BE-474A-85A5-65EC63AA7BB5}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {D8F25263-F4BE-474A-85A5-65EC63AA7BB5}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {D8F25263-F4BE-474A-85A5-65EC63AA7BB5}.Release|Any CPU.Build.0 = Release|Any CPU 24 | {43B3320B-CB65-4701-A37B-9650356FD8F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 25 | {43B3320B-CB65-4701-A37B-9650356FD8F6}.Debug|Any CPU.Build.0 = Debug|Any CPU 26 | {43B3320B-CB65-4701-A37B-9650356FD8F6}.Release|Any CPU.ActiveCfg = Release|Any CPU 27 | {43B3320B-CB65-4701-A37B-9650356FD8F6}.Release|Any CPU.Build.0 = Release|Any CPU 28 | {FE8F3381-C4CA-4290-865B-D717506A1E8A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 29 | {FE8F3381-C4CA-4290-865B-D717506A1E8A}.Debug|Any CPU.Build.0 = Debug|Any CPU 30 | {FE8F3381-C4CA-4290-865B-D717506A1E8A}.Release|Any CPU.ActiveCfg = Release|Any CPU 31 | {FE8F3381-C4CA-4290-865B-D717506A1E8A}.Release|Any CPU.Build.0 = Release|Any CPU 32 | {1379E7F6-F83C-493C-B0F4-2B194EC68932}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 33 | {1379E7F6-F83C-493C-B0F4-2B194EC68932}.Debug|Any CPU.Build.0 = Debug|Any CPU 34 | {1379E7F6-F83C-493C-B0F4-2B194EC68932}.Release|Any CPU.ActiveCfg = Release|Any CPU 35 | {1379E7F6-F83C-493C-B0F4-2B194EC68932}.Release|Any CPU.Build.0 = Release|Any CPU 36 | EndGlobalSection 37 | GlobalSection(SolutionProperties) = preSolution 38 | HideSolutionNode = FALSE 39 | EndGlobalSection 40 | GlobalSection(ExtensibilityGlobals) = postSolution 41 | SolutionGuid = {FE115B64-9C53-4116-A348-06F4525E935B} 42 | EndGlobalSection 43 | EndGlobal 44 | -------------------------------------------------------------------------------- /chapter-5-admin/extended/CommitCustomerDataExtended/CommitCustomerDataExtended.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.31320.298 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommitCustomerDataExtended", "CommitCustomerDataExtended\CommitCustomerDataExtended.csproj", "{76970018-0372-47F8-8E27-26370AB3ABF2}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CommitCustomerData.Extended.UnitTests", "..\..\tests\CommitCustomerData.Extended.UnitTests\CommitCustomerData.Extended.UnitTests.csproj", "{A30FE89A-C30D-409C-808B-761C842A8B5D}" 9 | EndProject 10 | Global 11 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 12 | Debug|Any CPU = Debug|Any CPU 13 | Release|Any CPU = Release|Any CPU 14 | EndGlobalSection 15 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 16 | {76970018-0372-47F8-8E27-26370AB3ABF2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {76970018-0372-47F8-8E27-26370AB3ABF2}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {76970018-0372-47F8-8E27-26370AB3ABF2}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {76970018-0372-47F8-8E27-26370AB3ABF2}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {A30FE89A-C30D-409C-808B-761C842A8B5D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {A30FE89A-C30D-409C-808B-761C842A8B5D}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {A30FE89A-C30D-409C-808B-761C842A8B5D}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {A30FE89A-C30D-409C-808B-761C842A8B5D}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | GlobalSection(ExtensibilityGlobals) = postSolution 29 | SolutionGuid = {F3EE42B9-4472-4146-B2DE-202469E1D25A} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /chapter-5-admin/extended/CommitCustomerDataExtended/CommitCustomerDataExtended/CommitCustomerData.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text.Json; 4 | 5 | namespace CommitCustomerDataExtended 6 | { 7 | public class CommitCustomerData 8 | { 9 | public void After(string parameter) 10 | { 11 | Console.WriteLine(parameter); 12 | 13 | using var doc = JsonDocument.Parse(parameter); 14 | var element = doc.RootElement; 15 | 16 | foreach (var eachElement in element.EnumerateArray()) 17 | { 18 | string name = eachElement.GetProperty("Name").GetString(); 19 | decimal creditLimit = eachElement.GetProperty("CreditLimit").GetDecimal(); 20 | 21 | if (creditLimit > 300) 22 | { 23 | Console.WriteLine($"{name} has a credit limit in excess of £300!"); 24 | } 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /chapter-5-admin/extended/CommitCustomerDataExtended/CommitCustomerDataExtended/CommitCustomerDataExtended.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5.0 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /chapter-5-admin/src/Admin/Admin.App/Admin.App.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | net5.0 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /chapter-5-admin/src/Admin/Admin.App/Program.cs: -------------------------------------------------------------------------------- 1 | using Admin.Common; 2 | using Admin.CustomerRead; 3 | using Admin.CustomerUpdate; 4 | using Admin.Extensibility; 5 | using System; 6 | using System.Collections; 7 | using System.Collections.Generic; 8 | using System.Linq; 9 | using System.Text.Json; 10 | 11 | namespace Admin.App 12 | { 13 | class Program 14 | { 15 | static List _customers = new List(); 16 | static Random _rnd = new Random(); 17 | static Hook _hook = new Hook(); 18 | 19 | static void Main(string[] args) 20 | { 21 | while (true) 22 | { 23 | Console.WriteLine("1 - Read Customer Data"); 24 | Console.WriteLine("2 - Write Customer Data"); 25 | Console.WriteLine("3 - Add Customer"); 26 | Console.WriteLine("0 - Exit"); 27 | 28 | var choice = Console.ReadKey(); 29 | switch (choice.Key) 30 | { 31 | case ConsoleKey.D0: 32 | return; 33 | 34 | case ConsoleKey.D1: 35 | ReadCustomerData(); 36 | foreach (var customer in _customers) 37 | { 38 | Console.WriteLine($"Customer: {customer.Name}"); 39 | } 40 | break; 41 | 42 | case ConsoleKey.D2: 43 | CommitCustomerData(); 44 | break; 45 | 46 | case ConsoleKey.D3: 47 | ReadCustomerData(); 48 | _customers.Add(new CustomerModel() 49 | { 50 | Name = $"Customer {Guid.NewGuid()}", 51 | Address = "Customer Address", 52 | CreditLimit = _rnd.Next(1000), 53 | Email = $"customer{_rnd.Next(10000)}@domain.com" 54 | }); 55 | CommitCustomerData(); 56 | 57 | break; 58 | 59 | } 60 | } 61 | } 62 | 63 | private static void ReadCustomerData() 64 | { 65 | var read = new ReadService(); 66 | _customers = read.ReadAll(@"c:\tmp\test.txt").ToList(); 67 | } 68 | 69 | private static void CommitCustomerData() 70 | { 71 | var write = new WriteService(); 72 | write.Write(_customers, @"c:\tmp\test.txt"); 73 | 74 | // Provide hook 75 | string jsonParams = JsonSerializer.Serialize(_customers); 76 | 77 | _hook.CreateHook( 78 | methodName: "After", 79 | className: "CommitCustomerData", 80 | parameters: new[] { jsonParams }); 81 | } 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /chapter-5-admin/src/Admin/Admin.Common/Admin.Common.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5.0 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /chapter-5-admin/src/Admin/Admin.Common/CustomerModel.cs: -------------------------------------------------------------------------------- 1 | namespace Admin.Common 2 | { 3 | public class CustomerModel 4 | { 5 | public string Name { get; set; } 6 | public string Address { get; set; } 7 | public string Email { get; set; } 8 | public decimal CreditLimit { get; set; } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /chapter-5-admin/src/Admin/Admin.CustomerRead/Admin.CustomerRead.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /chapter-5-admin/src/Admin/Admin.CustomerRead/IReadService.cs: -------------------------------------------------------------------------------- 1 | using Admin.Common; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Admin.CustomerRead 9 | { 10 | public interface IReadService 11 | { 12 | IEnumerable ReadAll(string dataFile); 13 | CustomerModel Read(string dataFile, string customer); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /chapter-5-admin/src/Admin/Admin.CustomerRead/ReadService.cs: -------------------------------------------------------------------------------- 1 | using Admin.Common; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Linq; 6 | using System.Text.Json; 7 | 8 | namespace Admin.CustomerRead 9 | { 10 | public class ReadService : IReadService 11 | { 12 | public CustomerModel Read(string dataFile, string customerName) 13 | { 14 | var customers = ReadAllRecords(dataFile); 15 | return customers.FirstOrDefault(a => a.Name == customerName); 16 | } 17 | 18 | public IEnumerable ReadAll(string dataFile) => 19 | ReadAllRecords(dataFile); 20 | 21 | private IEnumerable ReadAllRecords(string dataFile) 22 | { 23 | string customerData = File.ReadAllText(dataFile); 24 | var customers = JsonSerializer.Deserialize>(customerData); 25 | return customers; 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /chapter-5-admin/src/Admin/Admin.CustomerUpdate/Admin.CustomerUpdate.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /chapter-5-admin/src/Admin/Admin.CustomerUpdate/IWriteService.cs: -------------------------------------------------------------------------------- 1 | using Admin.Common; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace Admin.CustomerUpdate 9 | { 10 | public interface IWriteService 11 | { 12 | void Write(IEnumerable customers, string file); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /chapter-5-admin/src/Admin/Admin.CustomerUpdate/WriteService.cs: -------------------------------------------------------------------------------- 1 | using Admin.Common; 2 | using System; 3 | using System.Collections.Generic; 4 | using System.IO; 5 | using System.Text.Json; 6 | 7 | namespace Admin.CustomerUpdate 8 | { 9 | public class WriteService : IWriteService 10 | { 11 | public void Write(IEnumerable customers, string file) 12 | { 13 | string serialisedCustomers = JsonSerializer.Serialize(customers); 14 | File.WriteAllText(file, serialisedCustomers); 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /chapter-5-admin/src/Admin/Admin.Extensibility/Admin.Extensibility.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5.0 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /chapter-5-admin/src/Admin/Admin.Extensibility/Hook.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Diagnostics; 3 | using System.IO; 4 | using System.Reflection; 5 | using System.Runtime.CompilerServices; 6 | 7 | namespace Admin.Extensibility 8 | { 9 | public class Hook 10 | { 11 | // https://stackoverflow.com/questions/18362368/loading-dlls-at-runtime-in-c-sharp 12 | 13 | public void CreateHook([CallerMemberName]string methodName = null, string className = null, object[] parameters = null) 14 | { 15 | // If className is not supplied then attempt to infer it 16 | if (string.IsNullOrWhiteSpace(className)) 17 | { 18 | var stackTrace = new StackTrace(); 19 | className = stackTrace.GetFrame(1).GetMethod().GetType().Name; 20 | } 21 | 22 | // Check that we have the basic arguments 23 | if (string.IsNullOrWhiteSpace(methodName) || string.IsNullOrWhiteSpace(className)) 24 | { 25 | throw new ArgumentException("className and methodName cannot be null or empty"); 26 | } 27 | 28 | string executingPath = Assembly.GetExecutingAssembly().Location; 29 | string libraryFullPath = Path.Combine(Path.GetDirectoryName(executingPath), $"{className}Extended.dll"); 30 | 31 | var library = Assembly.LoadFile(libraryFullPath); 32 | 33 | foreach (Type type in library.GetExportedTypes()) 34 | { 35 | var c = Activator.CreateInstance(type); 36 | type.InvokeMember(methodName, BindingFlags.InvokeMethod, null, c, parameters); 37 | } 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /chapter-5-admin/src/Admin/Admin.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 16 4 | VisualStudioVersion = 16.0.31320.298 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Admin.App", "Admin.App\Admin.App.csproj", "{13A7B4C3-2DBF-4F63-B2D5-0E1B51C1AD95}" 7 | EndProject 8 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Admin.CustomerRead", "Admin.CustomerRead\Admin.CustomerRead.csproj", "{388EC51C-58AF-4510-B0D8-F08A32958CBD}" 9 | EndProject 10 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Admin.Common", "Admin.Common\Admin.Common.csproj", "{A7424E57-7151-4A04-9B32-920414F76B47}" 11 | EndProject 12 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Admin.CustomerUpdate", "Admin.CustomerUpdate\Admin.CustomerUpdate.csproj", "{27811ACC-6E49-484C-B2F1-B98A21D8D06F}" 13 | EndProject 14 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Admin.Extensibility", "Admin.Extensibility\Admin.Extensibility.csproj", "{E75C1552-D094-4EF8-9671-64241387D23F}" 15 | EndProject 16 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Tests", "Tests", "{6489376A-EFA1-400D-9645-51F89C3CFFED}" 17 | EndProject 18 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Admin.UnitTests", "..\..\tests\Admin.UnitTests\Admin.UnitTests.csproj", "{CF772593-E0DD-4E10-A711-C80ACBBD79C1}" 19 | EndProject 20 | Global 21 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 22 | Debug|Any CPU = Debug|Any CPU 23 | Release|Any CPU = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 26 | {13A7B4C3-2DBF-4F63-B2D5-0E1B51C1AD95}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {13A7B4C3-2DBF-4F63-B2D5-0E1B51C1AD95}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {13A7B4C3-2DBF-4F63-B2D5-0E1B51C1AD95}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {13A7B4C3-2DBF-4F63-B2D5-0E1B51C1AD95}.Release|Any CPU.Build.0 = Release|Any CPU 30 | {388EC51C-58AF-4510-B0D8-F08A32958CBD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 31 | {388EC51C-58AF-4510-B0D8-F08A32958CBD}.Debug|Any CPU.Build.0 = Debug|Any CPU 32 | {388EC51C-58AF-4510-B0D8-F08A32958CBD}.Release|Any CPU.ActiveCfg = Release|Any CPU 33 | {388EC51C-58AF-4510-B0D8-F08A32958CBD}.Release|Any CPU.Build.0 = Release|Any CPU 34 | {A7424E57-7151-4A04-9B32-920414F76B47}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 35 | {A7424E57-7151-4A04-9B32-920414F76B47}.Debug|Any CPU.Build.0 = Debug|Any CPU 36 | {A7424E57-7151-4A04-9B32-920414F76B47}.Release|Any CPU.ActiveCfg = Release|Any CPU 37 | {A7424E57-7151-4A04-9B32-920414F76B47}.Release|Any CPU.Build.0 = Release|Any CPU 38 | {27811ACC-6E49-484C-B2F1-B98A21D8D06F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 39 | {27811ACC-6E49-484C-B2F1-B98A21D8D06F}.Debug|Any CPU.Build.0 = Debug|Any CPU 40 | {27811ACC-6E49-484C-B2F1-B98A21D8D06F}.Release|Any CPU.ActiveCfg = Release|Any CPU 41 | {27811ACC-6E49-484C-B2F1-B98A21D8D06F}.Release|Any CPU.Build.0 = Release|Any CPU 42 | {E75C1552-D094-4EF8-9671-64241387D23F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 43 | {E75C1552-D094-4EF8-9671-64241387D23F}.Debug|Any CPU.Build.0 = Debug|Any CPU 44 | {E75C1552-D094-4EF8-9671-64241387D23F}.Release|Any CPU.ActiveCfg = Release|Any CPU 45 | {E75C1552-D094-4EF8-9671-64241387D23F}.Release|Any CPU.Build.0 = Release|Any CPU 46 | {CF772593-E0DD-4E10-A711-C80ACBBD79C1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 47 | {CF772593-E0DD-4E10-A711-C80ACBBD79C1}.Debug|Any CPU.Build.0 = Debug|Any CPU 48 | {CF772593-E0DD-4E10-A711-C80ACBBD79C1}.Release|Any CPU.ActiveCfg = Release|Any CPU 49 | {CF772593-E0DD-4E10-A711-C80ACBBD79C1}.Release|Any CPU.Build.0 = Release|Any CPU 50 | EndGlobalSection 51 | GlobalSection(SolutionProperties) = preSolution 52 | HideSolutionNode = FALSE 53 | EndGlobalSection 54 | GlobalSection(NestedProjects) = preSolution 55 | {CF772593-E0DD-4E10-A711-C80ACBBD79C1} = {6489376A-EFA1-400D-9645-51F89C3CFFED} 56 | EndGlobalSection 57 | GlobalSection(ExtensibilityGlobals) = postSolution 58 | SolutionGuid = {A4CCA110-5CA6-4447-B954-29E477E9947E} 59 | EndGlobalSection 60 | EndGlobal 61 | -------------------------------------------------------------------------------- /chapter-5-admin/tests/Admin.UnitTests/Admin.UnitTests.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | net5.0 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | all 13 | runtime; build; native; contentfiles; analyzers; buildtransitive 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /chapter-5-admin/tests/Admin.UnitTests/ExtensibilityTests.cs: -------------------------------------------------------------------------------- 1 | using Admin.Extensibility; 2 | using Microsoft.CodeAnalysis; 3 | using Microsoft.CodeAnalysis.CSharp; 4 | using Microsoft.CodeAnalysis.CSharp.Syntax; 5 | using Microsoft.CodeDom.Providers.DotNetCompilerPlatform; 6 | using System.Collections.Generic; 7 | using System.IO; 8 | using System.Linq; 9 | using System.Reflection; 10 | using System.Text; 11 | using Xunit; 12 | 13 | namespace Admin.UnitTests 14 | { 15 | public class ExtensibilityTests 16 | { 17 | 18 | 19 | [Fact] 20 | public void Hook_CallsCorrectMethod_NoParameters() 21 | { 22 | // Arrange 23 | string execLocation = Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location); 24 | string extensionLocation = Path.Combine(execLocation, @"..\..\..\..\..\extended\CommitCustomerDataExtended"); 25 | 26 | File.Copy(Path.Combine(extensionLocation, @"CommitCustomerDataExtended\bin\debug\net5.0\CommitCustomerDataExtended.dll"), 27 | Path.Combine(execLocation, "CommitCustomerDataExtended.dll"), true); 28 | var hook = new Hook(); 29 | 30 | // Act 31 | hook.CreateHook("After", "CommitCustomerData"); 32 | 33 | // Assert 34 | 35 | 36 | } 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /chapter-5-admin/tests/CommitCustomerData.Extended.UnitTests/CommitCustomerData.Extended.UnitTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net5.0 5 | 6 | 7 | 8 | 9 | 10 | all 11 | runtime; build; native; contentfiles; analyzers; buildtransitive 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | -------------------------------------------------------------------------------- /chapter-5-admin/tests/CommitCustomerData.Extended.UnitTests/CommitCustomerDataTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using Xunit; 3 | 4 | namespace CommitCustomerData.Extended.UnitTests 5 | { 6 | public class CommitCustomerDataTests 7 | { 8 | [Fact] 9 | public void CommitCustomerData_After_ParseJson() 10 | { 11 | // Arrange 12 | string json = "[{\"Name\":\"Customer bcc51794-dff1-4be0-a29d-9708a5e09ce7\",\"Address\":\"Customer Address\",\"Email\":\"customer1454@domain.com\",\"CreditLimit\":393},{\"Name\":\"Customer b7f84bc4-5c12-46cc-b68f-9aa9744f2fec\",\"Address\":\"Customer Address\",\"Email\":\"customer8013@domain.com\",\"CreditLimit\":385},{\"Name\":\"Customer ae86d250-feb8-44ba-9d78-7219e36cbd1f\",\"Address\":\"Customer Address\",\"Email\":\"customer8280@domain.com\",\"CreditLimit\":808},{\"Name\":\"Customer 26014c6f-99ac-43be-bedb-109c3ed1b6e5\",\"Address\":\"Customer Address\",\"Email\":\"customer8125@domain.com\",\"CreditLimit\":262},{\"Name\":\"Customer a422cc2b-e18e-4d02-a7ec-5b74f42cdaa5\",\"Address\":\"Customer Address\",\"Email\":\"customer7497@domain.com\",\"CreditLimit\":917},{\"Name\":\"Customer 3e1c0455-b5d7-44b6-a2e5-939d7a54ca42\",\"Address\":\"Customer Address\",\"Email\":\"customer9088@domain.com\",\"CreditLimit\":654},{\"Name\":\"Customer 2a0c65ca-0757-4f40-ac5d-767e06100f38\",\"Address\":\"Customer Address\",\"Email\":\"customer5048 @domain.com\",\"CreditLimit\":836}]"; 13 | var commitCustomerData = new CommitCustomerDataExtended.CommitCustomerData(); 14 | 15 | // Act 16 | commitCustomerData.After(json); 17 | 18 | // Assert 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /chapter-6-travel-rep/src/.dockerignore: -------------------------------------------------------------------------------- 1 | **/.classpath 2 | **/.dockerignore 3 | **/.env 4 | **/.git 5 | **/.gitignore 6 | **/.project 7 | **/.settings 8 | **/.toolstarget 9 | **/.vs 10 | **/.vscode 11 | **/*.*proj.user 12 | **/*.dbmdl 13 | **/*.jfm 14 | **/azds.yaml 15 | **/bin 16 | **/charts 17 | **/docker-compose* 18 | **/Dockerfile* 19 | **/node_modules 20 | **/npm-debug.log 21 | **/obj 22 | **/secrets.dev.yaml 23 | **/values.dev.yaml 24 | LICENSE 25 | README.md -------------------------------------------------------------------------------- /chapter-6-travel-rep/src/TravelRep.Ambassador/CentralSystemProxyService.cs: -------------------------------------------------------------------------------- 1 | using Hangfire; 2 | using TravelRep.Ambassador.Models; 3 | 4 | namespace TravelRep.Ambassador 5 | { 6 | public class CentralSystemProxyService : ICentralSystemProxyService 7 | { 8 | private readonly IHttpClientFactory _httpClientFactory; 9 | private readonly SystemConfiguration _systemConfiguration; 10 | private readonly ILogger _logger; 11 | 12 | public CentralSystemProxyService( 13 | IHttpClientFactory httpClientFactory, SystemConfiguration systemConfiguration, 14 | ILogger logger) 15 | { 16 | _httpClientFactory = httpClientFactory; 17 | _systemConfiguration = systemConfiguration; 18 | _logger = logger; 19 | } 20 | 21 | // Returns true for a successful call, 22 | // and false to indicate that it will continue to try 23 | public async Task CallCheckin(double longitude, double latitude) 24 | { 25 | Console.WriteLine($"CallCheckin({longitude}, {latitude})"); 26 | var result = await CallCentralSystemCheckin(longitude, latitude); 27 | if (result) return true; 28 | 29 | BackgroundJob.Enqueue(() => 30 | CallCentralSystemCheckinFireAndForget(longitude, latitude)); 31 | 32 | return false; 33 | } 34 | 35 | [AutomaticRetry(Attempts = 5, OnAttemptsExceeded = AttemptsExceededAction.Fail, 36 | DelaysInSeconds = new[] { 1, 3, 20, 60, 3600 })] 37 | public async Task CallCentralSystemCheckinFireAndForget(double longitude, double latitude) 38 | { 39 | Console.WriteLine($"CallCentralSystemCheckinFireAndForget({longitude}, {latitude})"); 40 | var result = await CallCentralSystemCheckin(longitude, latitude); 41 | if (result) throw new Exception("Unable to contact central system for checkin"); 42 | } 43 | 44 | public async Task CallCentralSystemCheckin(double longitude, double latitude) 45 | { 46 | try 47 | { 48 | var client = _httpClientFactory.CreateClient(); 49 | var content = new StringContent(""); 50 | string query = $"?longitude={longitude}&latitude={latitude}"; 51 | var result = await client.PostAsJsonAsync($"{_systemConfiguration.CentralSystem}/checkin{query}", content); 52 | if (result.IsSuccessStatusCode) return true; 53 | 54 | var results = await result.Content.ReadAsStringAsync(); 55 | _logger.LogWarning("Call failed:"); 56 | _logger.LogWarning(results); 57 | return false; 58 | } 59 | catch (Exception ex) 60 | { 61 | _logger.LogError(ex, ex.Message); 62 | return false; 63 | } 64 | } 65 | 66 | public async Task CallCancellation(string report, int flightNumber) 67 | { 68 | try 69 | { 70 | var client = _httpClientFactory.CreateClient(); 71 | var content = new StringContent(report); 72 | string query = $"?flightNumber={flightNumber}"; 73 | var result = await client.PostAsJsonAsync($"{_systemConfiguration.CentralSystem}/cancellation{query}", content); 74 | if (result.IsSuccessStatusCode) return true; 75 | 76 | var results = await result.Content.ReadAsStringAsync(); 77 | _logger.LogWarning("Call failed:"); 78 | _logger.LogWarning(results); 79 | return false; 80 | } 81 | catch (Exception ex) 82 | { 83 | _logger.LogError(ex, ex.Message); 84 | return false; 85 | } 86 | 87 | } 88 | 89 | public async Task CallComplaint(string complaintText) 90 | { 91 | try 92 | { 93 | var client = _httpClientFactory.CreateClient(); 94 | var content = new StringContent(complaintText); 95 | var result = await client.PostAsJsonAsync($"{_systemConfiguration.CentralSystem}/complaint", content); 96 | if (result.IsSuccessStatusCode) return true; 97 | 98 | var results = await result.Content.ReadAsStringAsync(); 99 | _logger.LogWarning("Call failed:"); 100 | _logger.LogWarning(results); 101 | return false; 102 | } 103 | catch (Exception ex) 104 | { 105 | _logger.LogError(ex, ex.Message); 106 | return false; 107 | } 108 | 109 | 110 | } 111 | } 112 | 113 | public interface ICentralSystemProxyService 114 | { 115 | Task CallCancellation(string report, int flightNumber); 116 | Task CallCheckin(double longitude, double latitude); 117 | Task CallComplaint(string complaintText); 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /chapter-6-travel-rep/src/TravelRep.Ambassador/Dockerfile: -------------------------------------------------------------------------------- 1 | #See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging. 2 | 3 | FROM mcr.microsoft.com/dotnet/aspnet:6.0 AS base 4 | WORKDIR /app 5 | EXPOSE 80 6 | EXPOSE 443 7 | 8 | FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build 9 | WORKDIR /src 10 | 11 | COPY ["TravelRep.Ambassador.csproj", "TravelRep.Ambassador/"] 12 | RUN dotnet restore "TravelRep.Ambassador/TravelRep.Ambassador.csproj" 13 | 14 | WORKDIR /src/TravelRep.Ambassador/ 15 | COPY . . 16 | 17 | RUN dotnet build "TravelRep.Ambassador.csproj" -c Release -o /app/build 18 | 19 | FROM build AS publish 20 | RUN dotnet publish "TravelRep.Ambassador.csproj" -c Release -o /app/publish 21 | 22 | FROM base AS final 23 | WORKDIR /app 24 | 25 | COPY mkcert /usr/local/bin 26 | COPY rootCA*.pem /root/.local/share/mkcert/ 27 | RUN chmod +x /usr/local/bin/mkcert \ 28 | && mkcert -install \ 29 | && rm -rf /usr/local/bin/mkcert 30 | 31 | COPY --from=publish /app/publish . 32 | ENTRYPOINT ["dotnet", "TravelRep.Ambassador.dll"] -------------------------------------------------------------------------------- /chapter-6-travel-rep/src/TravelRep.Ambassador/Models/Cancellation.cs: -------------------------------------------------------------------------------- 1 | namespace TravelRep.Ambassador.Models 2 | { 3 | public record Cancellation 4 | { 5 | public string? Report { get; set; } 6 | public int FlightNumber { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /chapter-6-travel-rep/src/TravelRep.Ambassador/Models/Complaint.cs: -------------------------------------------------------------------------------- 1 | namespace TravelRep.Ambassador.Models 2 | { 3 | public record Complaint 4 | { 5 | public string? Text { get; set; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /chapter-6-travel-rep/src/TravelRep.Ambassador/Models/Location.cs: -------------------------------------------------------------------------------- 1 | namespace TravelRep.Ambassador.Models 2 | { 3 | public record Location 4 | { 5 | public double Longitude { get; set; } 6 | public double Latitude { get; set; } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /chapter-6-travel-rep/src/TravelRep.Ambassador/Models/SystemConfiguration.cs: -------------------------------------------------------------------------------- 1 | namespace TravelRep.Ambassador.Models 2 | { 3 | public class SystemConfiguration 4 | { 5 | public string? CentralSystem { get; set; } 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /chapter-6-travel-rep/src/TravelRep.Ambassador/Program.cs: -------------------------------------------------------------------------------- 1 | using Hangfire; 2 | using Hangfire.Dashboard; 3 | using Hangfire.LiteDB; 4 | using Microsoft.AspNetCore.Mvc; 5 | using TravelRep.Ambassador; 6 | using TravelRep.Ambassador.Models; 7 | 8 | var builder = WebApplication.CreateBuilder(args); 9 | 10 | // Add services to the container. 11 | // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle 12 | builder.Services.AddEndpointsApiExplorer(); 13 | builder.Services.AddSwaggerGen(); 14 | builder.Services.AddHttpClient(); 15 | builder.Services.AddLogging(); 16 | 17 | builder.Services.AddHangfire(configuration => 18 | { 19 | configuration.UseLiteDbStorage("./hf.db"); 20 | 21 | }); 22 | builder.Services.AddHangfireServer(); 23 | 24 | var config = new SystemConfiguration() 25 | { 26 | CentralSystem = builder.Configuration["CentralSystemUrl"] 27 | }; 28 | builder.Services.AddSingleton(config); 29 | builder.Services.AddSingleton(); 30 | 31 | var app = builder.Build(); 32 | 33 | // Configure the HTTP request pipeline. 34 | if (app.Environment.IsDevelopment()) 35 | { 36 | app.UseSwagger(); 37 | app.UseSwaggerUI(); 38 | } 39 | 40 | app.UseHttpsRedirection(); 41 | 42 | var options = new DashboardOptions() 43 | { 44 | Authorization = new[] { new AuthorizationFilter() } 45 | }; 46 | app.UseHangfireDashboard("/hangfire", options); 47 | 48 | var logger = app.Services.GetRequiredService>(); 49 | 50 | app.MapPost("/checkin", async ([FromBody]Location location, ICentralSystemProxyService centralSystemProxyService, IHttpClientFactory httpClientFactory) => 51 | { 52 | var result = await centralSystemProxyService.CallCheckin(location.Longitude, location.Latitude); 53 | if (result) return Results.Ok(); 54 | return Results.Accepted(); 55 | }); 56 | app.MapPost("/cancellation", async ([FromBody]Cancellation cancellation, ICentralSystemProxyService centralSystemProxyService, IHttpClientFactory httpClientFactory) => 57 | { 58 | if (string.IsNullOrWhiteSpace(cancellation.Report)) return Results.BadRequest(cancellation); 59 | 60 | var result = await centralSystemProxyService.CallCancellation(cancellation.Report, cancellation.FlightNumber); 61 | if (result) return Results.Ok(); 62 | return Results.Accepted(); 63 | }); 64 | app.MapPost("/complaint", async ([FromBody] Complaint complaint, ICentralSystemProxyService centralSystemProxyService, IHttpClientFactory httpClientFactory) => 65 | { 66 | if (string.IsNullOrWhiteSpace(complaint.Text)) return Results.BadRequest(complaint); 67 | 68 | var result = await centralSystemProxyService.CallComplaint(complaint.Text); 69 | if (result) return Results.Ok(); 70 | return Results.Accepted(); 71 | }); 72 | 73 | app.Run(); 74 | 75 | public class AuthorizationFilter : IDashboardAuthorizationFilter 76 | { 77 | public bool Authorize(DashboardContext context) => true; 78 | } -------------------------------------------------------------------------------- /chapter-6-travel-rep/src/TravelRep.Ambassador/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:19107", 8 | "sslPort": 44327 9 | } 10 | }, 11 | "profiles": { 12 | "TravelRep.Ambassador": { 13 | "commandName": "Project", 14 | "launchBrowser": true, 15 | "launchUrl": "swagger", 16 | "environmentVariables": { 17 | "ASPNETCORE_ENVIRONMENT": "Development" 18 | }, 19 | "applicationUrl": "https://localhost:7084;http://localhost:5084", 20 | "dotnetRunMessages": true 21 | }, 22 | "IIS Express": { 23 | "commandName": "IISExpress", 24 | "launchBrowser": true, 25 | "launchUrl": "swagger", 26 | "environmentVariables": { 27 | "ASPNETCORE_ENVIRONMENT": "Development" 28 | } 29 | }, 30 | "Docker": { 31 | "commandName": "Docker", 32 | "launchBrowser": true, 33 | "launchUrl": "{Scheme}://{ServiceHost}:{ServicePort}/swagger", 34 | "publishAllPorts": true, 35 | "useSSL": true 36 | } 37 | } 38 | } -------------------------------------------------------------------------------- /chapter-6-travel-rep/src/TravelRep.Ambassador/TravelRep.Ambassador.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | enable 7 | dd04c9b2-85fe-4e27-b368-8a433197c3df 8 | Linux 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /chapter-6-travel-rep/src/TravelRep.Ambassador/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /chapter-6-travel-rep/src/TravelRep.Ambassador/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*", 9 | "CentralSystemUrl": "https://host.docker.internal:7266" 10 | 11 | } 12 | -------------------------------------------------------------------------------- /chapter-6-travel-rep/src/TravelRep.Ambassador/hf-log.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/software-architecture-by-example/8b6d6eb127394f1381faf7aeceba14df3caf441f/chapter-6-travel-rep/src/TravelRep.Ambassador/hf-log.db -------------------------------------------------------------------------------- /chapter-6-travel-rep/src/TravelRep.Ambassador/hf.db: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/software-architecture-by-example/8b6d6eb127394f1381faf7aeceba14df3caf441f/chapter-6-travel-rep/src/TravelRep.Ambassador/hf.db -------------------------------------------------------------------------------- /chapter-6-travel-rep/src/TravelRep.Ambassador/mkcert: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Apress/software-architecture-by-example/8b6d6eb127394f1381faf7aeceba14df3caf441f/chapter-6-travel-rep/src/TravelRep.Ambassador/mkcert -------------------------------------------------------------------------------- /chapter-6-travel-rep/src/TravelRep.App/Dockerfile: -------------------------------------------------------------------------------- 1 | #See https://aka.ms/containerfastmode to understand how Visual Studio uses this Dockerfile to build your images for faster debugging. 2 | 3 | FROM mcr.microsoft.com/dotnet/runtime:6.0 AS base 4 | WORKDIR /app 5 | 6 | FROM mcr.microsoft.com/dotnet/sdk:6.0 AS build 7 | WORKDIR /src 8 | 9 | COPY ["TravelRep.App.csproj", "TravelRep.App/"] 10 | RUN dotnet restore "TravelRep.App/TravelRep.App.csproj" 11 | COPY . . 12 | 13 | RUN dotnet build "TravelRep.App.csproj" -c Release -o /app/build 14 | 15 | FROM build AS publish 16 | RUN dotnet publish "TravelRep.App.csproj" -c Release -o /app/publish 17 | 18 | FROM base AS final 19 | WORKDIR /app 20 | COPY --from=publish /app/publish . 21 | ENTRYPOINT ["dotnet", "TravelRep.App.dll"] -------------------------------------------------------------------------------- /chapter-6-travel-rep/src/TravelRep.App/Models/Cancellation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TravelRep.App 8 | { 9 | public record Cancellation 10 | { 11 | public string? Report { get; set; } 12 | public int FlightNumber { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /chapter-6-travel-rep/src/TravelRep.App/Models/Complaint.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TravelRep.App 8 | { 9 | public record Complaint 10 | { 11 | public string? Text { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /chapter-6-travel-rep/src/TravelRep.App/Models/Location.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TravelRep.App 8 | { 9 | public record Location 10 | { 11 | public double Longitude { get; set; } 12 | public double Latitude { get; set; } 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /chapter-6-travel-rep/src/TravelRep.App/Models/SystemConfiguration.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace TravelRep.App.Models 8 | { 9 | public class SystemConfiguration 10 | { 11 | public string AmbassadorBaseUrl { get; set; } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /chapter-6-travel-rep/src/TravelRep.App/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.Extensions.Configuration; 2 | using Microsoft.Extensions.DependencyInjection; 3 | using Microsoft.Extensions.Hosting; 4 | using System.Text; 5 | using System.Text.Json; 6 | using TravelRep.App; 7 | using TravelRep.App.Models; 8 | 9 | var _random = new Random(); 10 | 11 | var serviceProvider = new ServiceCollection() 12 | .AddHttpClient() 13 | .BuildServiceProvider(); 14 | 15 | IConfiguration config = new ConfigurationBuilder() 16 | .AddJsonFile("appsettings.json") 17 | .AddEnvironmentVariables() 18 | .Build(); 19 | var systemConfiguration = new SystemConfiguration() 20 | { 21 | AmbassadorBaseUrl = config["AmbassadorBaseUrl"] 22 | }; 23 | 24 | while (true) 25 | { 26 | Console.WriteLine("Please select function"); 27 | Console.WriteLine("0 - Exit"); 28 | Console.WriteLine("1 - Check-in"); 29 | Console.WriteLine("2 - Cancellation"); 30 | Console.WriteLine("3 - Complaint"); 31 | 32 | Console.WriteLine("5 - Stress Test"); 33 | 34 | var choice = Console.ReadKey(); 35 | 36 | switch (choice.Key) 37 | { 38 | case ConsoleKey.D0: 39 | return; 40 | 41 | case ConsoleKey.D1: 42 | await CallCheckin(); 43 | break; 44 | 45 | case ConsoleKey.D2: 46 | await CallCancellation(); 47 | break; 48 | 49 | case ConsoleKey.D3: 50 | await CallComplaint(); 51 | break; 52 | 53 | case ConsoleKey.D5: 54 | Console.WriteLine("\n\nStarting Stress Test..."); 55 | Parallel.For(0, 1000, async a => 56 | { 57 | switch (_random.Next(2)) 58 | { 59 | case 0: 60 | await CallCheckin(); 61 | break; 62 | case 1: 63 | await CallComplaint(); 64 | break; 65 | case 2: 66 | await CallComplaint(); 67 | break; 68 | } 69 | 70 | }); 71 | break; 72 | } 73 | } 74 | 75 | async Task CallCheckin() 76 | { 77 | var httpFactory = serviceProvider.GetRequiredService(); 78 | var httpClient = httpFactory.CreateClient(); 79 | httpClient.BaseAddress = new Uri(systemConfiguration.AmbassadorBaseUrl); 80 | 81 | var location = new TravelRep.App.Location() 82 | { 83 | Latitude = 12, 84 | Longitude = 256 85 | }; 86 | Console.WriteLine("\n\nCalling checkin API..."); 87 | var content = new StringContent(JsonSerializer.Serialize(location), Encoding.UTF32, "application/json"); 88 | var result = await httpClient.PostAsync("checkin", content); 89 | result.EnsureSuccessStatusCode(); 90 | 91 | Console.WriteLine("Reading results..."); 92 | var results = await result.Content.ReadAsStringAsync(); 93 | Console.WriteLine(results); 94 | } 95 | 96 | async Task CallCancellation() 97 | { 98 | var httpFactory = serviceProvider.GetRequiredService(); 99 | var httpClient = httpFactory.CreateClient(); 100 | httpClient.BaseAddress = new Uri(systemConfiguration.AmbassadorBaseUrl); 101 | 102 | var cancellation = new TravelRep.App.Cancellation() 103 | { 104 | Report = "Test report", 105 | FlightNumber = 116 106 | }; 107 | Console.WriteLine("\n\nCalling cancellation API..."); 108 | 109 | var options = new JsonSerializerOptions() 110 | { 111 | PropertyNamingPolicy = JsonNamingPolicy.CamelCase 112 | }; 113 | var serialisedContents = JsonSerializer.Serialize(cancellation, typeof(Cancellation), options); 114 | 115 | var content = new StringContent(serialisedContents, Encoding.UTF32, "application/json"); 116 | var result = await httpClient.PostAsync("cancellation", content); 117 | 118 | result.EnsureSuccessStatusCode(); 119 | 120 | Console.WriteLine("Reading results..."); 121 | var results = await result.Content.ReadAsStringAsync(); 122 | Console.WriteLine(results); 123 | } 124 | 125 | async Task CallComplaint() 126 | { 127 | var httpFactory = serviceProvider.GetRequiredService(); 128 | var httpClient = httpFactory.CreateClient(); 129 | httpClient.BaseAddress = new Uri(systemConfiguration.AmbassadorBaseUrl); 130 | 131 | var complaint = new Complaint() 132 | { 133 | Text = "test complaint text" 134 | }; 135 | Console.WriteLine("\n\nCalling complaint API..."); 136 | var content = new StringContent(JsonSerializer.Serialize(complaint), Encoding.UTF32, "application/json"); 137 | var result = await httpClient.PostAsync("complaint", content); 138 | result.EnsureSuccessStatusCode(); 139 | 140 | Console.WriteLine("Reading results..."); 141 | var results = await result.Content.ReadAsStringAsync(); 142 | Console.WriteLine(results); 143 | } 144 | -------------------------------------------------------------------------------- /chapter-6-travel-rep/src/TravelRep.App/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "profiles": { 3 | "TravelRep.App": { 4 | "commandName": "Project" 5 | }, 6 | "Docker": { 7 | "commandName": "Docker" 8 | } 9 | } 10 | } -------------------------------------------------------------------------------- /chapter-6-travel-rep/src/TravelRep.App/TravelRep.App.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Exe 5 | net6.0 6 | enable 7 | enable 8 | Linux 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | PreserveNewest 18 | true 19 | PreserveNewest 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /chapter-6-travel-rep/src/TravelRep.App/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*", 9 | "AmbassadorBaseUrl": "http://ambassador-api" 10 | 11 | } 12 | -------------------------------------------------------------------------------- /chapter-6-travel-rep/src/TravelRep.CentralApi/Program.cs: -------------------------------------------------------------------------------- 1 | using Microsoft.AspNetCore.Mvc; 2 | 3 | Random _random = new Random(); 4 | 5 | async Task ChaosMonkey() 6 | { 7 | Console.WriteLine($"\n\nChaosMonkey Invoked: {DateTime.Now}"); 8 | 9 | int result = _random.Next(5); 10 | switch (result) 11 | { 12 | case 0: 13 | Console.WriteLine($"Throw exception immediately"); 14 | throw new Exception("Failure"); 15 | 16 | case 1: 17 | Console.WriteLine($"Wait, then throw exception"); 18 | await Task.Delay(3000); 19 | throw new Exception("Failure"); 20 | 21 | case 2: 22 | Console.WriteLine($"Wait, then work"); 23 | await Task.Delay(3000); 24 | break; 25 | } 26 | Console.WriteLine("Call succeeded"); 27 | } 28 | 29 | var builder = WebApplication.CreateBuilder(args); 30 | 31 | // Add services to the container. 32 | // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle 33 | builder.Services.AddEndpointsApiExplorer(); 34 | builder.Services.AddSwaggerGen(); 35 | 36 | var app = builder.Build(); 37 | 38 | // Configure the HTTP request pipeline. 39 | if (app.Environment.IsDevelopment()) 40 | { 41 | app.UseSwagger(); 42 | app.UseSwaggerUI(); 43 | } 44 | 45 | app.UseHttpsRedirection(); 46 | 47 | app.MapPost("/checkin", async (double longitude, double latitude) => 48 | { 49 | Console.Write($"\n\n/checkin called: {DateTime.Now}\n\n\n"); 50 | 51 | await ChaosMonkey(); 52 | return Results.Ok(); 53 | }); 54 | 55 | app.MapPost("/cancellation", async ([FromBody]string report, int flightNumber) => 56 | { 57 | await ChaosMonkey(); 58 | return Results.Ok(); 59 | }); 60 | 61 | app.MapPost("/complaint", async ([FromBody]string complaint) => 62 | { 63 | await ChaosMonkey(); 64 | return Results.Ok(); 65 | }); 66 | 67 | 68 | app.Run(); 69 | -------------------------------------------------------------------------------- /chapter-6-travel-rep/src/TravelRep.CentralApi/Properties/launchSettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "$schema": "https://json.schemastore.org/launchsettings.json", 3 | "iisSettings": { 4 | "windowsAuthentication": false, 5 | "anonymousAuthentication": true, 6 | "iisExpress": { 7 | "applicationUrl": "http://localhost:58416", 8 | "sslPort": 44388 9 | } 10 | }, 11 | "profiles": { 12 | "TravelRep.CentralApi": { 13 | "commandName": "Project", 14 | "dotnetRunMessages": true, 15 | "launchBrowser": true, 16 | "launchUrl": "swagger", 17 | "applicationUrl": "https://localhost:7266;http://localhost:5266", 18 | "environmentVariables": { 19 | "ASPNETCORE_ENVIRONMENT": "Development" 20 | } 21 | }, 22 | "IIS Express": { 23 | "commandName": "IISExpress", 24 | "launchBrowser": true, 25 | "launchUrl": "swagger", 26 | "environmentVariables": { 27 | "ASPNETCORE_ENVIRONMENT": "Development" 28 | } 29 | } 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /chapter-6-travel-rep/src/TravelRep.CentralApi/TravelRep.CentralApi.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | net6.0 5 | enable 6 | enable 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /chapter-6-travel-rep/src/TravelRep.CentralApi/appsettings.Development.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /chapter-6-travel-rep/src/TravelRep.CentralApi/appsettings.json: -------------------------------------------------------------------------------- 1 | { 2 | "Logging": { 3 | "LogLevel": { 4 | "Default": "Information", 5 | "Microsoft.AspNetCore": "Warning" 6 | } 7 | }, 8 | "AllowedHosts": "*", 9 | "Kestrel": { 10 | "Certificates": { 11 | "Default": { 12 | "Path": "localhost-host.docker.internal.pem", 13 | "KeyPath": "localhost-host.docker.internal-key.pem" 14 | } 15 | } 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /chapter-6-travel-rep/src/TravelRep.CentralApi/localhost-host.docker.internal-key.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDFeTf2wLjjH+CJ 3 | I2HzGluHg0Wkb6WFRIt9wUDXxWQEKGtPtwVTD1Xt1XVA737EEf0dD4u3+Gv8s1h8 4 | wKetVkIi9Fi3FTptv3X1F72eDMDtxbzvhRfG0/vUtqxVHKLhuCh4sQkJGdHEJ+0V 5 | lTeVWSYHquxogrPS1DRojvwp5zBDWCbjA0fYGXsrYYY2YetUmuxTP/1Orzy6kWsm 6 | tQl3owYSupKUJiyz1aU9Oo2q008rHHmS2vNaYWS5XrgnR5faDEPEITW6WB5Y5uKH 7 | XVpDUhOYpRo6FtZqBbPoV5GGq+ZuHlMisfn5PKdMXLxSimA3q7W+VHnQRRs6YGvM 8 | NGCsMOrJAgMBAAECggEACmFd3L2yulWxouPWQUYwa4YuAf/Qbpn0zy56bx00LbfG 9 | NFGFmSEkPjVLTZeJTVs4t+9aQ9huLMFstFoYNldjcJzxkOYvxRccZz7RO96lwZn0 10 | XY7cehookbzsgCjaMw1QK0/7Dpxp12rV3p2SZ2phQmPsu22rcDixbJGflcBOXCvN 11 | bIwNsbbNF+oRYhrR/nej+/JFYTLZ7XmnSyPuR8Peu4PcUL9xT0DwspbVA+CLojzo 12 | QVRz2Y9bzuveIogtDzIXVVbmI3+35YXwTTN1T5cpKIMMjLK6O3thaSAGWQHmRUPf 13 | jrAvef9R2s1sQ9jSP/Kp8+6zs5ghUlWaUF5QgCQzpQKBgQDV3RpAnERLzJ+EN1NO 14 | NjP/1RvJ3FIO/+kWmyY+Wehg0KXsB3pAjKgb5g2nWPYff3702j9t/5Mn/CjR+QUW 15 | ktmyUYLcUY7Sb2l2tZOAYLTc7kSAPHaTSltgdCTmswPHXiB63knwCTzoAxoWTA2U 16 | kTtQNhs3Y668PzCsrd9IzJ6K1wKBgQDsYWzpYM14llSGs8PsnBaigpTB3YCwkNoP 17 | rK8DUUoaMcM4ASJeiiQaR/oGvaWjY1hfeVEMYEoC55d2+3uIFko74l1KvZXYHkM/ 18 | vjA2jAHZSAyNOmQ5cp/uNI5B0tIpFQi1E5p7xIreHbGW2I6fc9cG4/LXGQAfCiDp 19 | GyomxqAjXwKBgQCpdsFlry3jDmrcXuaxE/xSXbDeXKndXuV5V0mPoLilds/zXt19 20 | prdTHBF3qOOmg+W66fHXHOyjg4lpELT6dTTxuutB35KXp1bfjPQrhvXqFxfKsZ6Y 21 | ChSsMnxHHlUBNtHv6pKWFQvhIQwb3Pnp3scsQSOB2YhNfJj6S/ChhBqhEwKBgCtz 22 | kUCpMbv77lfAjcBdxO9kj5G/dNuAFQWSma1DcRq1kQwncTjtrctC9dvtWphZN7J9 23 | K3/Z52D3T2louwF+D3FIoBuQFA5hKb166YPtYp2dV+lSFYBV6L4x3QCOj2FL44jd 24 | rKUZsOk5jh6LelQnSByVJFuT1ejexMP04V+1XVSDAoGALG6e7VrqrizgTPbPKj2E 25 | LM8d7ApikQlSCpBCYrJ2syzQgrNHiJigxkuaebhYDeqTqlLusgFB7OsmpPkFslFf 26 | OAydOqi5q4gdYT1/pXF2eviaLntHm/qOKKkbzLRLgDKInmRGqItN4jMKshPfH0d8 27 | Wlv5E+WCeERMgXoxHVPy3Hw= 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /chapter-6-travel-rep/src/TravelRep.CentralApi/localhost-host.docker.internal.pem: -------------------------------------------------------------------------------- 1 | -----BEGIN CERTIFICATE----- 2 | MIIEjzCCAvegAwIBAgIQaTEd9+lBd29ZCJlNr3XN8jANBgkqhkiG9w0BAQsFADCB 3 | pzEeMBwGA1UEChMVbWtjZXJ0IGRldmVsb3BtZW50IENBMT4wPAYDVQQLDDVMQVBU 4 | T1AtUEFFTjczRVBccGNtaWNATEFQVE9QLVBBRU43M0VQIChQYXVsIE1pY2hhZWxz 5 | KTFFMEMGA1UEAww8bWtjZXJ0IExBUFRPUC1QQUVONzNFUFxwY21pY0BMQVBUT1At 6 | UEFFTjczRVAgKFBhdWwgTWljaGFlbHMpMB4XDTIyMDEwMzE3MjMzNloXDTI0MDQw 7 | MzE2MjMzNlowaTEnMCUGA1UEChMebWtjZXJ0IGRldmVsb3BtZW50IGNlcnRpZmlj 8 | YXRlMT4wPAYDVQQLDDVMQVBUT1AtUEFFTjczRVBccGNtaWNATEFQVE9QLVBBRU43 9 | M0VQIChQYXVsIE1pY2hhZWxzKTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoC 10 | ggEBAMV5N/bAuOMf4IkjYfMaW4eDRaRvpYVEi33BQNfFZAQoa0+3BVMPVe3VdUDv 11 | fsQR/R0Pi7f4a/yzWHzAp61WQiL0WLcVOm2/dfUXvZ4MwO3FvO+FF8bT+9S2rFUc 12 | ouG4KHixCQkZ0cQn7RWVN5VZJgeq7GiCs9LUNGiO/CnnMENYJuMDR9gZeythhjZh 13 | 61Sa7FM//U6vPLqRaya1CXejBhK6kpQmLLPVpT06jarTTysceZLa81phZLleuCdH 14 | l9oMQ8QhNbpYHljm4oddWkNSE5ilGjoW1moFs+hXkYar5m4eUyKx+fk8p0xcvFKK 15 | YDertb5UedBFGzpga8w0YKww6skCAwEAAaN0MHIwDgYDVR0PAQH/BAQDAgWgMBMG 16 | A1UdJQQMMAoGCCsGAQUFBwMBMB8GA1UdIwQYMBaAFFMgEJR51hq/82nbiNS3nvnq 17 | UzTJMCoGA1UdEQQjMCGCCWxvY2FsaG9zdIIUaG9zdC5kb2NrZXIuaW50ZXJuYWww 18 | DQYJKoZIhvcNAQELBQADggGBAFtw1nnl4uzAFqEXLOsRd2KTf7ljNQu1R0k+WGo4 19 | yIQupOhpbsrzEtp9143KJGXDDJ6pJiAQoE+eWrUo/cbZateTwDI9VRbPMaGpBMkd 20 | sn58m0B2HRECJ72JNd/E6rBuLSvJhZqMeTvlSTBlRqu/lJyFNPrf6smD1EnBesH3 21 | cWZVabGN2+0fsbiyJdUlJ9hm/2XPhBWAREGsiLst0ee3Nl7NZ6SH23o+N00VJ/sj 22 | ZoMyBNpF2aBFo2cgxyB9jlRS1EfL1jYp+ykU5hEFdZKsT1pRGJi9EsiwDQJd+3kV 23 | 2fyiNvpA5sSL+3Nsb3V9UTtV0xz/8PdFvqpHmpUjdh35fY5RWMMJfPw3k1xPMChD 24 | iZABRi1w2HLY12XlQocjq6NR460TtJBqODYXKDUKXXpKBPBRZJio5le9Q4M6bUI0 25 | LXm88kGdp/OhP8tWOcZYepjevXI/FZOGwZMy4O1KiQd6GWct1yk/RhJbPweappHq 26 | vkXiMOEiqh+uMqqHHWGQiIZcmA== 27 | -----END CERTIFICATE----- 28 | -------------------------------------------------------------------------------- /chapter-6-travel-rep/src/TravelRep.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio Version 17 4 | VisualStudioVersion = 17.0.31710.8 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TravelRep.App", "TravelRep.App\TravelRep.App.csproj", "{EE571813-FE56-49E5-800E-3D85CE7B16D8}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TravelRep.Ambassador", "TravelRep.Ambassador\TravelRep.Ambassador.csproj", "{9ED4B275-BA7F-4DDB-941A-EF784EDFD3F6}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TravelRep.CentralApi", "TravelRep.CentralApi\TravelRep.CentralApi.csproj", "{3CC994A1-E4E9-4D5C-800D-67034224D1F2}" 11 | EndProject 12 | Global 13 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 14 | Debug|Any CPU = Debug|Any CPU 15 | Release|Any CPU = Release|Any CPU 16 | EndGlobalSection 17 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 18 | {EE571813-FE56-49E5-800E-3D85CE7B16D8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 19 | {EE571813-FE56-49E5-800E-3D85CE7B16D8}.Debug|Any CPU.Build.0 = Debug|Any CPU 20 | {EE571813-FE56-49E5-800E-3D85CE7B16D8}.Release|Any CPU.ActiveCfg = Release|Any CPU 21 | {EE571813-FE56-49E5-800E-3D85CE7B16D8}.Release|Any CPU.Build.0 = Release|Any CPU 22 | {9ED4B275-BA7F-4DDB-941A-EF784EDFD3F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 23 | {9ED4B275-BA7F-4DDB-941A-EF784EDFD3F6}.Debug|Any CPU.Build.0 = Debug|Any CPU 24 | {9ED4B275-BA7F-4DDB-941A-EF784EDFD3F6}.Release|Any CPU.ActiveCfg = Release|Any CPU 25 | {9ED4B275-BA7F-4DDB-941A-EF784EDFD3F6}.Release|Any CPU.Build.0 = Release|Any CPU 26 | {3CC994A1-E4E9-4D5C-800D-67034224D1F2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 27 | {3CC994A1-E4E9-4D5C-800D-67034224D1F2}.Debug|Any CPU.Build.0 = Debug|Any CPU 28 | {3CC994A1-E4E9-4D5C-800D-67034224D1F2}.Release|Any CPU.ActiveCfg = Release|Any CPU 29 | {3CC994A1-E4E9-4D5C-800D-67034224D1F2}.Release|Any CPU.Build.0 = Release|Any CPU 30 | EndGlobalSection 31 | GlobalSection(SolutionProperties) = preSolution 32 | HideSolutionNode = FALSE 33 | EndGlobalSection 34 | GlobalSection(ExtensibilityGlobals) = postSolution 35 | SolutionGuid = {A7CD7F65-1DA4-4664-AD5D-BB94F4C1B751} 36 | EndGlobalSection 37 | EndGlobal 38 | -------------------------------------------------------------------------------- /chapter-6-travel-rep/src/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | ambassador-api: 4 | build: .\TravelRep.Ambassador 5 | ports: 6 | - "5010:80" 7 | logging: 8 | driver: "json-file" 9 | 10 | 11 | main-app: 12 | build: .\TravelRep.App 13 | stdin_open: true 14 | tty: true 15 | depends_on: 16 | - "ambassador-api" 17 | 18 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Still under development 2 | 3 | 4 | # Software Architecture By Example 5 | 6 | This repository is a companion to the book, which can be purchased here 7 | 8 | 9 | # Running the Code 10 | 11 | 12 | --------------------------------------------------------------------------------