├── .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 |
--------------------------------------------------------------------------------