├── .gitignore ├── BookingApi.UnitTests ├── BookingApi.UnitTests.csproj ├── BookingApiTestConventionsAttribute.cs ├── FakeReservationsRepository.cs ├── MaîtreDEnvy.cs ├── MaîtreDTests.cs └── ReservationsIntegrationTests.cs ├── BookingApi.sln ├── BookingApi ├── BookingApi.csproj ├── ControllerBase.cs ├── IActionResult.cs ├── IReservationsRepository.cs ├── InternalServerErrorActionResult.cs ├── Maybe.cs ├── MaîtreD.cs ├── OkActionResult.cs ├── Reservation.cs ├── ReservationsController.cs ├── TaskEnvy.cs └── TaskMaybe.cs ├── Directory.Build.targets ├── LICENSE ├── README.md ├── after.BookingApi.sln.targets └── build.sh /.gitignore: -------------------------------------------------------------------------------- 1 | # Build Folders (you can keep bin if you'd like, to store dlls and pdbs) 2 | [Bb]in/ 3 | [Oo]bj/ 4 | 5 | # MsTest test results 6 | TestResults 7 | 8 | ## Ignore Visual Studio temporary files, build results, and 9 | ## files generated by popular Visual Studio add-ons. 10 | 11 | # User-specific files 12 | *.suo 13 | *.user 14 | *.sln.docstates 15 | .vs/ 16 | 17 | # Build results 18 | [Dd]ebug/ 19 | [Rr]elease/ 20 | x64/ 21 | *_i.c 22 | *_p.c 23 | *.ilk 24 | *.meta 25 | *.obj 26 | *.pch 27 | *.pgc 28 | *.pgd 29 | *.rsp 30 | *.sbr 31 | *.tlb 32 | *.tli 33 | *.tlh 34 | *.tmp 35 | *.log 36 | *.vspscc 37 | *.vssscc 38 | .builds 39 | 40 | # Visual C++ cache files 41 | ipch/ 42 | *.aps 43 | *.ncb 44 | *.opensdf 45 | *.sdf 46 | 47 | # Visual Studio profiler 48 | *.psess 49 | *.vsp 50 | *.vspx 51 | 52 | # Guidance Automation Toolkit 53 | *.gpState 54 | 55 | # ReSharper is a .NET coding add-in 56 | _ReSharper* 57 | 58 | # NCrunch 59 | *.ncrunch* 60 | .*crunch*.local.xml 61 | 62 | # Installshield output folder 63 | [Ee]xpress 64 | 65 | # DocProject is a documentation generator add-in 66 | DocProject/buildhelp/ 67 | DocProject/Help/*.HxT 68 | DocProject/Help/*.HxC 69 | DocProject/Help/*.hhc 70 | DocProject/Help/*.hhk 71 | DocProject/Help/*.hhp 72 | DocProject/Help/Html2 73 | DocProject/Help/html 74 | 75 | # Click-Once directory 76 | publish 77 | 78 | # Publish Web Output 79 | *.Publish.xml 80 | 81 | # Windows Azure Build Output 82 | csx 83 | *.build.csdef 84 | 85 | # Windows Store app package directory 86 | AppPackages/ 87 | 88 | # Backup & report files from converting an old project file to a newer 89 | # Visual Studio version. Backup files are not needed, because we have git ;-) 90 | _Upgr/ 91 | Backup*/ 92 | UpgradeLog*.XML 93 | 94 | # FAKE hidden directory 95 | .fake/ 96 | -------------------------------------------------------------------------------- /BookingApi.UnitTests/BookingApi.UnitTests.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.0 5 | 6 | false 7 | 8 | Ploeh.Samples.BookingApi.UnitTests 9 | 10 | Ploeh.Samples.BookingApi.UnitTests 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /BookingApi.UnitTests/BookingApiTestConventionsAttribute.cs: -------------------------------------------------------------------------------- 1 | using AutoFixture; 2 | using AutoFixture.AutoMoq; 3 | using AutoFixture.Xunit2; 4 | using System; 5 | 6 | namespace Ploeh.Samples.BookingApi.UnitTests 7 | { 8 | public class BookingApiTestConventionsAttribute : AutoDataAttribute 9 | { 10 | public BookingApiTestConventionsAttribute() : 11 | base(() => new Fixture().Customize(new AutoMoqCustomization())) 12 | { 13 | } 14 | } 15 | } -------------------------------------------------------------------------------- /BookingApi.UnitTests/FakeReservationsRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Collections.Generic; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace Ploeh.Samples.BookingApi.UnitTests 8 | { 9 | public class FakeReservationsRepository : IReservationsRepository 10 | { 11 | private readonly List reservations; 12 | 13 | public FakeReservationsRepository() 14 | { 15 | reservations = new List(); 16 | } 17 | 18 | public Task Create(Reservation reservation) 19 | { 20 | reservations.Add(reservation); 21 | // Hardly a robut implementation, since indices will be reused, 22 | // but should be good enough for the purpose of a pair of 23 | // integration tests 24 | return Task.FromResult(reservations.IndexOf(reservation)); 25 | } 26 | 27 | public Task ReadReservations(DateTimeOffset date) 28 | { 29 | var firstTick = date.Date; 30 | var lastTick = firstTick.AddDays(1).AddTicks(-1); 31 | var filteredReservations = reservations 32 | .Where(r => firstTick <= r.Date && r.Date <= lastTick) 33 | .ToArray(); 34 | return Task.FromResult(filteredReservations); 35 | } 36 | 37 | public bool Contains(Reservation reservation) 38 | { 39 | return reservations.Contains(reservation); 40 | } 41 | 42 | public int GetId(Reservation reservation) 43 | { 44 | return reservations.IndexOf(reservation); 45 | } 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /BookingApi.UnitTests/MaîtreDEnvy.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Ploeh.Samples.BookingApi.UnitTests 6 | { 7 | public static class MaîtreDEnvy 8 | { 9 | public static MaîtreD WithCapacity( 10 | this MaîtreD maîtreD, 11 | int newCapacity) 12 | { 13 | return new MaîtreD(newCapacity); 14 | } 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /BookingApi.UnitTests/MaîtreDTests.cs: -------------------------------------------------------------------------------- 1 | using AutoFixture.Xunit2; 2 | using Moq; 3 | using System; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | using Xunit; 9 | 10 | namespace Ploeh.Samples.BookingApi.UnitTests 11 | { 12 | public class MaîtreDTests 13 | { 14 | [Theory, BookingApiTestConventions] 15 | public void TryAcceptReturnsReservationInHappyPathScenario( 16 | Reservation reservation, 17 | Reservation[] reservations, 18 | MaîtreD sut, 19 | int excessCapacity) 20 | { 21 | var reservedSeats = reservations.Sum(r => r.Quantity); 22 | sut = sut.WithCapacity( 23 | reservedSeats + reservation.Quantity + excessCapacity); 24 | 25 | var actual = sut.TryAccept(reservations, reservation); 26 | 27 | Assert.Equal(new Maybe(reservation.Accept()), actual); 28 | } 29 | 30 | [Theory, BookingApiTestConventions] 31 | public void TryAcceptReturnsNothingOnInsufficientCapacity( 32 | Reservation reservation, 33 | Reservation[] reservations, 34 | MaîtreD sut) 35 | { 36 | var reservedSeats = reservations.Sum(r => r.Quantity); 37 | sut = sut.WithCapacity(reservedSeats + reservation.Quantity - 1); 38 | 39 | var actual = sut.TryAccept(reservations, reservation); 40 | 41 | Assert.True(actual.IsNothing); 42 | Assert.False(reservation.IsAccepted); 43 | } 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /BookingApi.UnitTests/ReservationsIntegrationTests.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | using Xunit; 6 | 7 | namespace Ploeh.Samples.BookingApi.UnitTests 8 | { 9 | public class ReservationsIntegrationTests 10 | { 11 | [Fact] 12 | public async Task ReservationSucceeds() 13 | { 14 | var repo = new FakeReservationsRepository(); 15 | var sut = new ReservationsController(10, repo); 16 | 17 | var reservation = new Reservation( 18 | date: new DateTimeOffset(2018, 8, 13, 16, 53, 0, TimeSpan.FromHours(2)), 19 | email: "mark@example.com", 20 | name: "Mark Seemann", 21 | quantity: 4); 22 | var actual = await sut.Post(reservation); 23 | 24 | Assert.True(repo.Contains(reservation.Accept())); 25 | var expectedId = repo.GetId(reservation.Accept()); 26 | var ok = Assert.IsAssignableFrom(actual); 27 | Assert.Equal(expectedId, ok.Value); 28 | } 29 | 30 | [Fact] 31 | public async Task ReservationFails() 32 | { 33 | var repo = new FakeReservationsRepository(); 34 | var sut = new ReservationsController(10, repo); 35 | 36 | var reservation = new Reservation( 37 | date: new DateTimeOffset(2018, 8, 13, 16, 53, 0, TimeSpan.FromHours(2)), 38 | email: "mark@example.com", 39 | name: "Mark Seemann", 40 | quantity: 11); 41 | var actual = await sut.Post(reservation); 42 | 43 | Assert.False(reservation.IsAccepted); 44 | Assert.False(repo.Contains(reservation)); 45 | Assert.IsAssignableFrom(actual); 46 | } 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /BookingApi.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 15 4 | VisualStudioVersion = 15.0.27703.2047 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BookingApi", "BookingApi\BookingApi.csproj", "{1707524A-22F0-4684-8254-9F3525D6756A}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BookingApi.UnitTests", "BookingApi.UnitTests\BookingApi.UnitTests.csproj", "{17D3308D-2ACC-44B5-B675-6CD334E1802F}" 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 | {1707524A-22F0-4684-8254-9F3525D6756A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {1707524A-22F0-4684-8254-9F3525D6756A}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {1707524A-22F0-4684-8254-9F3525D6756A}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {1707524A-22F0-4684-8254-9F3525D6756A}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {17D3308D-2ACC-44B5-B675-6CD334E1802F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {17D3308D-2ACC-44B5-B675-6CD334E1802F}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {17D3308D-2ACC-44B5-B675-6CD334E1802F}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {17D3308D-2ACC-44B5-B675-6CD334E1802F}.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 = {1FC8C2A1-C4AE-412D-8ADE-347A28420E9C} 30 | EndGlobalSection 31 | EndGlobal 32 | -------------------------------------------------------------------------------- /BookingApi/BookingApi.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | netcoreapp2.0 5 | Ploeh.Samples.BookingApi 6 | Ploeh.Samples.BookingApi 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /BookingApi/ControllerBase.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | 3 | namespace Ploeh.Samples.BookingApi 4 | { 5 | public abstract class ControllerBase 6 | { 7 | // This signature is odd, since it specifically takes an int, and an 8 | // int only, as an argument. Normally, one would expect that the type 9 | // the argument is untyped as any object, or else that the method is 10 | // generically typed, and that the value is of the type T. 11 | // This specific type, though, is to catch type errors in the example 12 | // code, in lieu of unit tests. 13 | public IActionResult Ok(int value) 14 | { 15 | return new OkActionResult(value); 16 | } 17 | 18 | public IActionResult InternalServerError(string msg) 19 | { 20 | return new InternalServerErrorActionResult(msg); 21 | } 22 | } 23 | } -------------------------------------------------------------------------------- /BookingApi/IActionResult.cs: -------------------------------------------------------------------------------- 1 | namespace Ploeh.Samples.BookingApi 2 | { 3 | public interface IActionResult 4 | { 5 | } 6 | } -------------------------------------------------------------------------------- /BookingApi/IReservationsRepository.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Threading.Tasks; 3 | 4 | namespace Ploeh.Samples.BookingApi 5 | { 6 | public interface IReservationsRepository 7 | { 8 | Task ReadReservations(DateTimeOffset date); 9 | 10 | Task Create(Reservation reservation); 11 | } 12 | } -------------------------------------------------------------------------------- /BookingApi/InternalServerErrorActionResult.cs: -------------------------------------------------------------------------------- 1 | namespace Ploeh.Samples.BookingApi 2 | { 3 | public class InternalServerErrorActionResult : IActionResult 4 | { 5 | public InternalServerErrorActionResult(string msg) 6 | { 7 | Msg = msg; 8 | } 9 | 10 | public string Msg { get; } 11 | } 12 | } -------------------------------------------------------------------------------- /BookingApi/Maybe.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Ploeh.Samples.BookingApi 6 | { 7 | public sealed class Maybe 8 | { 9 | private readonly bool hasItem; 10 | private readonly T item; 11 | 12 | public Maybe() 13 | { 14 | hasItem = false; 15 | } 16 | 17 | public Maybe(T item) 18 | { 19 | if (item == null) 20 | throw new ArgumentNullException(nameof(item)); 21 | 22 | hasItem = true; 23 | this.item = item; 24 | } 25 | 26 | public TResult Match(TResult nothing, Func just) 27 | { 28 | if (nothing == null) 29 | throw new ArgumentNullException(nameof(nothing)); 30 | if (just == null) 31 | throw new ArgumentNullException(nameof(just)); 32 | 33 | return hasItem ? just(item) : nothing; 34 | } 35 | 36 | public Maybe Select(Func selector) 37 | { 38 | return Match( 39 | nothing: new Maybe(), 40 | just: x => new Maybe(selector(x))); 41 | } 42 | 43 | public Maybe SelectMany( 44 | Func> selector) 45 | { 46 | return Match(nothing: new Maybe(), just: selector); 47 | } 48 | 49 | public bool IsNothing => Match(nothing: true, just: _ => false); 50 | 51 | public bool IsJust => !IsNothing; 52 | 53 | public override bool Equals(object obj) 54 | { 55 | if (!(obj is Maybe other)) 56 | return false; 57 | 58 | return Match( 59 | nothing: !other.hasItem, 60 | just: x => other.Match( 61 | nothing: !hasItem, 62 | just: y => Equals(x, y))); 63 | 64 | } 65 | 66 | public override int GetHashCode() 67 | { 68 | return Match(nothing: 0, just: x => x.GetHashCode()); 69 | } 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /BookingApi/MaîtreD.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 Ploeh.Samples.BookingApi 8 | { 9 | public class MaîtreD 10 | { 11 | public MaîtreD(int capacity) 12 | { 13 | Capacity = capacity; 14 | } 15 | 16 | public int Capacity { get; } 17 | 18 | public Maybe TryAccept( 19 | Reservation[] reservations, 20 | Reservation reservation) 21 | { 22 | int reservedSeats = reservations.Sum(r => r.Quantity); 23 | 24 | if (Capacity < reservedSeats + reservation.Quantity) 25 | return new Maybe(); 26 | 27 | return new Maybe(reservation.Accept()); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /BookingApi/OkActionResult.cs: -------------------------------------------------------------------------------- 1 | namespace Ploeh.Samples.BookingApi 2 | { 3 | public class OkActionResult : IActionResult 4 | { 5 | public OkActionResult(int value) 6 | { 7 | Value = value; 8 | } 9 | 10 | public int Value { get; } 11 | } 12 | } -------------------------------------------------------------------------------- /BookingApi/Reservation.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | 5 | namespace Ploeh.Samples.BookingApi 6 | { 7 | public sealed class Reservation 8 | { 9 | public Reservation( 10 | DateTimeOffset date, 11 | string email, 12 | string name, 13 | int quantity) : this(date, email, name, quantity, false) 14 | { } 15 | 16 | private Reservation(DateTimeOffset date, 17 | string email, 18 | string name, 19 | int quantity, 20 | bool isAccepted) 21 | { 22 | Date = date; 23 | Email = email; 24 | Name = name; 25 | Quantity = quantity; 26 | IsAccepted = isAccepted; 27 | } 28 | 29 | public DateTimeOffset Date { get; } 30 | public string Email { get; } 31 | public string Name { get; } 32 | public int Quantity { get; } 33 | public bool IsAccepted { get; } 34 | 35 | public Reservation Accept() 36 | { 37 | return new Reservation( 38 | Date, 39 | Email, 40 | Name, 41 | Quantity, 42 | true); 43 | } 44 | 45 | public override bool Equals(object obj) 46 | { 47 | if (!(obj is Reservation other)) 48 | return false; 49 | 50 | return Equals(Date, other.Date) 51 | && Equals(Email, other.Email) 52 | && Equals(Name, other.Name) 53 | && Equals(Quantity, other.Quantity) 54 | && Equals(IsAccepted, other.IsAccepted); 55 | } 56 | 57 | public override int GetHashCode() 58 | { 59 | return 60 | Date.GetHashCode() ^ 61 | Email.GetHashCode() ^ 62 | Name.GetHashCode() ^ 63 | Quantity.GetHashCode() ^ 64 | IsAccepted.GetHashCode(); 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /BookingApi/ReservationsController.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | 6 | namespace Ploeh.Samples.BookingApi 7 | { 8 | public class ReservationsController : ControllerBase 9 | { 10 | public ReservationsController( 11 | int capacity, 12 | IReservationsRepository repository) 13 | { 14 | Capacity = capacity; 15 | Repository = repository; 16 | maîtreD = new MaîtreD(capacity); 17 | } 18 | 19 | public int Capacity { get; } 20 | public IReservationsRepository Repository { get; } 21 | 22 | private readonly MaîtreD maîtreD; 23 | 24 | public async Task Post(Reservation reservation) 25 | { 26 | return await Repository.ReadReservations(reservation.Date) 27 | .Select(rs => maîtreD.TryAccept(rs, reservation)) 28 | .SelectMany(m => m.Traverse(Repository.Create)) 29 | .Match(InternalServerError("Table unavailable"), Ok); 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /BookingApi/TaskEnvy.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | 6 | namespace Ploeh.Samples.BookingApi 7 | { 8 | public static class TaskEnvy 9 | { 10 | public async static Task Select( 11 | this Task source, 12 | Func selector) 13 | { 14 | var x = await source; 15 | return selector(x); 16 | } 17 | 18 | public async static Task SelectMany( 19 | this Task source, 20 | Func> selector) 21 | { 22 | var x = await source; 23 | return await selector(x); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /BookingApi/TaskMaybe.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Text; 4 | using System.Threading.Tasks; 5 | 6 | namespace Ploeh.Samples.BookingApi 7 | { 8 | public static class TaskMaybe 9 | { 10 | public static async Task Match( 11 | this Task> source, 12 | TResult nothing, 13 | Func just) 14 | { 15 | var m = await source; 16 | return m.Match(nothing: nothing, just: just); 17 | } 18 | 19 | public static async Task> Select( 20 | this Task> source, 21 | Func selector) 22 | { 23 | var m = await source; 24 | return m.Select(selector); 25 | } 26 | 27 | public static async Task> SelectMany( 28 | this Task> source, 29 | Func>> selector) 30 | { 31 | var m = await source; 32 | return await m.Match( 33 | nothing: Task.FromResult(new Maybe()), 34 | just: x => selector(x)); 35 | } 36 | 37 | public static Task> Traverse( 38 | this Maybe source, 39 | Func> selector) 40 | { 41 | return source.Match( 42 | nothing: Task.FromResult(new Maybe()), 43 | just: async x => new Maybe(await selector(x))); 44 | } 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /Directory.Build.targets: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Mark Seemann 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Asynchronous Injection 2 | 3 | Sample code supporting the blog article [Asynchronous Injection](http://blog.ploeh.dk/2019/02/11/asynchronous-injection). 4 | 5 | Additionally, the `use-monad-stack` branch supports the article [Nested monads](https://blog.ploeh.dk/2024/11/25/nested-monads). 6 | -------------------------------------------------------------------------------- /after.BookingApi.sln.targets: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | dotnet test 3 | --------------------------------------------------------------------------------