├── .gitattributes ├── .gitignore ├── .paket ├── paket.bootstrapper.exe └── paket.targets ├── README.md ├── build.cmd ├── data ├── FootballResults.csv ├── FootballResultsBad.csv ├── adventureworks-lt.dacpac └── sample-package.html ├── drivers └── readme.txt ├── paket.dependencies ├── paket.lock └── src └── code-listings ├── appendix-05.fsx ├── lesson-02.fsx ├── lesson-03.fsx ├── lesson-04.fsx ├── lesson-05.fsx ├── lesson-06.fsx ├── lesson-07.fsx ├── lesson-08 ├── App.config ├── Capstone1.fsproj ├── Capstone1.sln ├── Car - Solution.fs ├── Car.fs ├── Program.fs ├── paket.references └── scratchpad.fsx ├── lesson-09.fsx ├── lesson-10.fsx ├── lesson-11.fsx ├── lesson-12 ├── App.config ├── AssemblyInfo.fs ├── Domain.fs ├── Operations.fs ├── Program.fs ├── lesson-12.fsx ├── modules-and-namespaces.fsproj └── modules-and-namespaces.sln ├── lesson-13.fsx ├── lesson-14 ├── App.config ├── Auditing.fs ├── Capstone2.fsproj ├── Capstone2.fsx ├── Capstone2.sln ├── Domain.fs ├── Operations.fs └── Program.fs ├── lesson-15.fsx ├── lesson-16.fsx ├── lesson-17.fsx ├── lesson-18.fsx ├── lesson-19 ├── App.config ├── Auditing.fs ├── Capstone3.fsproj ├── Capstone3.sln ├── Domain.fs ├── FileRepository.fs ├── Operations.fs ├── Program.fs ├── Scratchpad.fsx └── sample-solution │ ├── App.config │ ├── Auditing.fs │ ├── Capstone3.fsproj │ ├── Capstone3.sln │ ├── Domain.fs │ ├── FileRepository.fs │ ├── Operations.fs │ ├── Program.fs │ └── Scratchpad.fsx ├── lesson-20.fsx ├── lesson-21.fsx ├── lesson-22.fsx ├── lesson-23-1.fsx ├── lesson-23-2.fsx ├── lesson-23-3.fsx ├── lesson-24 ├── App.config ├── Auditing.fs ├── Capstone4.fsproj ├── Capstone4.sln ├── Domain.fs ├── FileRepository.fs ├── Listings.fsx ├── Operations.fs ├── Program.fs ├── Scratchpad.fsx └── sample-solution │ ├── App.config │ ├── Auditing.fs │ ├── Capstone4.fsproj │ ├── Capstone4.sln │ ├── Domain.fs │ ├── FileRepository.fs │ ├── Operations.fs │ ├── Program.fs │ └── Scratchpad.fsx ├── lesson-25 ├── CSharpProject │ ├── CSharpProject.csproj │ ├── Class1.cs │ └── Properties │ │ └── AssemblyInfo.cs ├── FSharpProject │ ├── App.config │ ├── AssemblyInfo.fs │ ├── FSharpProject.fsproj │ └── Program.fs ├── HybridSolution.sln └── Scratchpad.fsx ├── lesson-26 ├── NuGetSample.sln └── NugetFSharp │ ├── App.config │ ├── AssemblyInfo.fs │ ├── Library1.fs │ ├── NugetFSharp.fsproj │ ├── Script1.fsx │ ├── Scripts │ ├── load-project-debug.fsx │ └── load-references-debug.fsx │ └── packages.config ├── lesson-27 ├── CSharpApp │ ├── App.config │ ├── CSharpApp.csproj │ ├── Program.cs │ └── Properties │ │ └── AssemblyInfo.cs ├── FSharpCode │ ├── AssemblyInfo.fs │ ├── FSharpCode.fsproj │ ├── Library1.fs │ └── Script.fsx └── Interop.sln ├── lesson-28.fsx ├── lesson-29 ├── Capstone5.sln ├── Core │ ├── Api.fs │ ├── App.config │ ├── Auditing.fs │ ├── Core.fsproj │ ├── Domain.fs │ ├── FileRepository.fs │ ├── Operations.fs │ ├── Program.fs │ └── Scratchpad.fsx ├── WpfClient │ ├── App.xaml │ ├── App.xaml.cs │ ├── Command.cs │ ├── FodyWeavers.xml │ ├── MainViewModel.cs │ ├── MainWindow.xaml │ ├── MainWindow.xaml.cs │ ├── Properties │ │ ├── AssemblyInfo.cs │ │ ├── Resources.Designer.cs │ │ ├── Resources.resx │ │ ├── Settings.Designer.cs │ │ └── Settings.settings │ ├── WpfClient.csproj │ ├── app.config │ └── packages.config └── sample-solution │ ├── Capstone5.sln │ ├── Core │ ├── Api.fs │ ├── App.config │ ├── Auditing.fs │ ├── Core.fsproj │ ├── Domain.fs │ ├── FileRepository.fs │ ├── FodyWeavers.xml │ ├── Operations.fs │ ├── Scratchpad.fsx │ └── packages.config │ └── WpfClient │ ├── App.xaml │ ├── App.xaml.cs │ ├── Command.cs │ ├── FodyWeavers.xml │ ├── MainViewModel.cs │ ├── MainWindow.xaml │ ├── MainWindow.xaml.cs │ ├── Properties │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ ├── Resources.resx │ ├── Settings.Designer.cs │ └── Settings.settings │ ├── WpfClient.csproj │ ├── app.config │ └── packages.config ├── lesson-30-1.fsx ├── lesson-30-2.fsx ├── lesson-31.fsx ├── lesson-32-1.fsx ├── lesson-32-2.fsx ├── lesson-33.fsx ├── lesson-34 ├── SqlDemo │ ├── App.config │ ├── AssemblyInfo.fs │ ├── CustomerRepository.fs │ ├── DataAccessThroughScript.fsx │ ├── Program.fs │ ├── Scripts │ │ ├── load-project-debug.fsx │ │ └── load-references-debug.fsx │ └── SqlDemo.fsproj └── TypeProviderConfig.sln ├── lesson-35 ├── BankAccountDb │ ├── Account.sql │ ├── AccountTransaction.sql │ ├── BankAccountDb.publish.xml │ ├── BankAccountDb.refactorlog │ ├── BankAccountDb.sqlproj │ ├── Operation.sql │ └── Script.PostDeployment.sql ├── Capstone6.sln ├── Core │ ├── Api.fs │ ├── App.config │ ├── Core.fsproj │ ├── Domain.fs │ ├── FileRepository.fs │ ├── Operations.fs │ ├── Scratchpad.fsx │ ├── SqlRepository.fs │ └── packages.config ├── WpfClient │ ├── App.xaml │ ├── App.xaml.cs │ ├── BankOperationConverter.cs │ ├── Command.cs │ ├── FodyWeavers.xml │ ├── MainViewModel.cs │ ├── MainWindow.xaml │ ├── MainWindow.xaml.cs │ ├── Properties │ │ ├── AssemblyInfo.cs │ │ ├── Resources.Designer.cs │ │ ├── Resources.resx │ │ ├── Settings.Designer.cs │ │ └── Settings.settings │ ├── WpfClient.csproj │ ├── app.config │ └── packages.config └── sample-solution │ ├── BankAccountDb │ ├── Account.sql │ ├── AccountTransaction.sql │ ├── BankAccountDb.publish.xml │ ├── BankAccountDb.refactorlog │ ├── BankAccountDb.sqlproj │ ├── Operation.sql │ └── Script.PostDeployment.sql │ ├── Capstone6.sln │ ├── Core │ ├── Api.fs │ ├── App.config │ ├── Core.fsproj │ ├── Domain.fs │ ├── FileRepository.fs │ ├── FodyWeavers.xml │ ├── Operations.fs │ ├── Scratchpad.fsx │ ├── SqlRepository.fs │ └── packages.config │ └── WpfClient │ ├── App.xaml │ ├── App.xaml.cs │ ├── BankOperationConverter.cs │ ├── Command.cs │ ├── FodyWeavers.xml │ ├── MainViewModel.cs │ ├── MainWindow.xaml │ ├── MainWindow.xaml.cs │ ├── Properties │ ├── AssemblyInfo.cs │ ├── Resources.Designer.cs │ ├── Resources.resx │ ├── Settings.Designer.cs │ └── Settings.settings │ ├── WpfClient.csproj │ ├── app.config │ └── packages.config ├── lesson-36.fsx ├── lesson-37 ├── .vs │ └── config │ │ └── applicationhost.config ├── FSharpWeb.sln ├── FSharpWeb │ ├── AssemblyInfo.fs │ ├── Controller.fs │ ├── FSharpWeb.fsproj │ ├── Startup.fs │ ├── Web.config │ ├── packages.config │ └── readme.txt ├── SuaveApp │ ├── App.config │ ├── AssemblyInfo.fs │ ├── Program.fs │ ├── SuaveApp.fsproj │ └── packages.config └── WebConsole │ ├── App.config │ ├── AssemblyInfo.fs │ ├── Controller.fs │ ├── Program.fs │ ├── WebConsole.fsproj │ └── packages.config ├── lesson-38 ├── .vs │ └── config │ │ └── applicationhost.config ├── FSharpWeb.sln ├── FSharpWeb │ ├── App_Start │ │ └── SwaggerConfig.cs │ ├── AssemblyInfo.fs │ ├── Controller.fs │ ├── FSharpWeb.fsproj │ ├── Startup.fs │ ├── Web.config │ ├── packages.config │ └── readme.txt ├── auth-sample.fsx └── code-listings.fsx ├── lesson-39 ├── .vs │ └── config │ │ └── applicationhost.config ├── Capstone7.sln ├── Core │ ├── Api.fs │ ├── App.config │ ├── Core.fsproj │ ├── Domain.fs │ ├── FileRepository.fs │ ├── Operations.fs │ ├── Scratchpad.fsx │ ├── SqlRepository.fs │ └── packages.config ├── Web │ ├── AssemblyInfo.fs │ ├── Controller.fs │ ├── Startup.fs │ ├── Web.config │ ├── Web.fsproj │ └── packages.config ├── code-listings.fsx └── sample-solution │ ├── .vs │ └── config │ │ └── applicationhost.config │ ├── Capstone7.sln │ ├── Core │ ├── Api.fs │ ├── App.config │ ├── Core.fsproj │ ├── Domain.fs │ ├── FileRepository.fs │ ├── Operations.fs │ ├── Scratchpad.fsx │ ├── SqlRepository.fs │ └── packages.config │ ├── Web │ ├── AssemblyInfo.fs │ ├── Controller.fs │ ├── Startup.fs │ ├── Web.config │ ├── Web.fsproj │ └── packages.config │ └── code-listings.fsx ├── lesson-40 └── UnitTesting │ ├── UnitTesting.sln │ └── UnitTesting │ ├── App.config │ ├── AssemblyInfo.fs │ ├── BusinessLogic.fs │ ├── BusinessLogicTests.fs │ ├── UnitTesting.fsproj │ └── packages.config ├── lesson-41.fsx ├── lesson-42.fsx └── lesson-43 ├── Capstone8.sln ├── Core ├── Api.fs ├── App.config ├── Core.fsproj ├── Domain.fs ├── FileRepository.fs ├── Operations.fs ├── Scratchpad.fsx ├── SqlRepository.fs └── packages.config ├── Tests ├── ApiTests.fs ├── App.config ├── AssemblyInfo.fs ├── Helpers.fs ├── PropertyTests.fs ├── Tests.fsproj ├── WebTests.fs └── packages.config ├── Web ├── AssemblyInfo.fs ├── Controller.fs ├── Startup.fs ├── Web.config ├── Web.fsproj └── packages.config └── sample-solution ├── .vs └── config │ └── applicationhost.config ├── Capstone8.sln ├── Core ├── Api.fs ├── App.config ├── Core.fsproj ├── Domain.fs ├── FileRepository.fs ├── Operations.fs ├── Scratchpad.fsx ├── SqlRepository.fs └── packages.config ├── Tests ├── ApiTests.fs ├── App.config ├── AssemblyInfo.fs ├── PropertyTests.fs ├── Tests.fsproj ├── WebTests.fs └── packages.config ├── Web ├── AssemblyInfo.fs ├── Controller.fs ├── Startup.fs ├── Web.config ├── Web.fsproj └── packages.config └── code-listings.fsx /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | #ignore thumbnails created by windows 3 | Thumbs.db 4 | #Ignore files build by Visual Studio 5 | *.obj 6 | *.exe 7 | *.pdb 8 | *.user 9 | *.aps 10 | *.pch 11 | *.vspscc 12 | *_i.c 13 | *_p.c 14 | *.ncb 15 | *.suo 16 | *.tlb 17 | *.tlh 18 | *.bak 19 | *.cache 20 | *.ilk 21 | *.log 22 | [Bb]in 23 | [Dd]ebug*/ 24 | *.lib 25 | *.sbr 26 | obj/ 27 | [Rr]elease*/ 28 | _ReSharper*/ 29 | [Tt]est[Rr]esult* 30 | **/packages/* 31 | *.dbmdl 32 | *.jfm 33 | -------------------------------------------------------------------------------- /.paket/paket.bootstrapper.exe: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isaacabraham/get-programming-fsharp/199056ff6ef693fb7ed48b018ad53b15aa8437ad/.paket/paket.bootstrapper.exe -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Get Programming with F# 2 | 3 | This repository contains all F# code listings as required for all exercises and samples within the 'Get Programming with F#' book, available [here]( https://www.manning.com/books/get-programming-with-f-sharp). 4 | 5 | To install all dependencies as needed for the exercises, simply run the build.cmd file, which uses Paket to download all NuGet dependencies. 6 | 7 | ## Using VS Code / VS2017 / VS2019 and .NET Core? 8 | Switch to the `netcore` branch of this repository. Many of the lessons and modules have been upgraded to the "new" project structure and should "just work"! 9 | -------------------------------------------------------------------------------- /build.cmd: -------------------------------------------------------------------------------- 1 | .paket\paket.bootstrapper.exe 2 | .paket\paket.exe restore 3 | -------------------------------------------------------------------------------- /data/adventureworks-lt.dacpac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/isaacabraham/get-programming-fsharp/199056ff6ef693fb7ed48b018ad53b15aa8437ad/data/adventureworks-lt.dacpac -------------------------------------------------------------------------------- /drivers/readme.txt: -------------------------------------------------------------------------------- 1 | Copy your web drivers in here as needed in lesson 42! You can find the Chromium driver at https://sites.google.com/a/chromium.org/chromedriver/. -------------------------------------------------------------------------------- /paket.dependencies: -------------------------------------------------------------------------------- 1 | source https://www.nuget.org/api/v2 2 | framework net452 3 | 4 | nuget FSharp.Core 5 | nuget FSharp.Data 6 | nuget XPlot.GoogleCharts 7 | nuget FSharp.Data.SqlClient 8 | nuget SqlProvider 9 | nuget Http.fs 1.5.1 10 | nuget SwaggerProvider 11 | nuget FSCheck.Xunit 12 | nuget Canopy -------------------------------------------------------------------------------- /paket.lock: -------------------------------------------------------------------------------- 1 | FRAMEWORK: NET452 2 | NUGET 3 | remote: https://www.nuget.org/api/v2 4 | canopy (1.1.2) 5 | FSharp.Core (>= 3.0.2) 6 | Selenium.WebDriver (3.0) 7 | FsCheck (2.7) 8 | FSharp.Core (>= 3.1.2.5) 9 | FsCheck.Xunit (2.7) 10 | FsCheck (>= 2.7) 11 | xunit.extensibility.execution (>= 2.1 < 3.0) 12 | FSharp.Core (4.0.0.1) 13 | FSharp.Data (2.3.2) 14 | FSharp.Data.SqlClient (1.8.2) 15 | Google.DataTable.Net.Wrapper (3.1.2) 16 | Http.fs (1.5.1) 17 | Newtonsoft.Json (9.0.1) 18 | Selenium.WebDriver (3.0) 19 | SQLProvider (1.0.37) 20 | SwaggerProvider (0.5.6) 21 | Newtonsoft.Json (>= 9.0.1) 22 | YamlDotNet (>= 3.9) 23 | XPlot.GoogleCharts (1.4.2) 24 | Google.DataTable.Net.Wrapper (3.1.2) 25 | Newtonsoft.Json (9.0.1) 26 | xunit.abstractions (2.0) 27 | xunit.extensibility.core (2.1) 28 | xunit.abstractions (2.0) 29 | xunit.extensibility.execution (2.1) 30 | xunit.extensibility.core (2.1) 31 | YamlDotNet (4.0) 32 | -------------------------------------------------------------------------------- /src/code-listings/lesson-02.fsx: -------------------------------------------------------------------------------- 1 | // Sample application entry point 2 | let main argv = 3 | printfn "%A" argv 4 | 0 // return an integer exit code 5 | 6 | // Creating an array of items 7 | let items = [| "item"; "item"; "item"; "item" |] 8 | 9 | [] 10 | let enhancedMain argv = 11 | let items = argv.Length 12 | printfn "Passed in %d items: %A" items argv 13 | 0 // return an integer exit code 14 | 15 | -------------------------------------------------------------------------------- /src/code-listings/lesson-03.fsx: -------------------------------------------------------------------------------- 1 | // Listing 3.1 2 | let currentTime = System.DateTime.UtcNow;; 3 | currentTime.TimeOfDay.ToString();; 4 | 5 | // Listing 3.2 6 | let greetPerson name age = 7 | sprintf "Hello, %s. You are %d years old" name age 8 | 9 | let greeting = greetPerson "Fred" 25 10 | 11 | -------------------------------------------------------------------------------- /src/code-listings/lesson-05.fsx: -------------------------------------------------------------------------------- 1 | // Listing 5.6 2 | let add (a:int, b:int) : int = 3 | let answer:int = a + b 4 | answer 5 | 6 | // Listing 5.7 7 | let getLength name = sprintf "Name is %d letters." name.Length 8 | let getLength (name:string) = sprintf "Name is %d letters." name.Length 9 | let foo(name) = "Hello! " + getLength(name) 10 | 11 | // Listing 5.8 12 | open System.Collections.Generic 13 | let numbers = List<_>() 14 | numbers.Add(10) 15 | numbers.Add(20) 16 | 17 | let otherNumbers = List() 18 | otherNumbers.Add(10) 19 | otherNumbers.Add(20) 20 | 21 | // Listing 5.9 22 | let createList(first, second) = 23 | let output = List() 24 | output.Add(first) 25 | output.Add(second) 26 | output 27 | 28 | // Listing 5.10 29 | let sayHello(someValue) = 30 | let innerFunction(number) = 31 | if number > 10 then "Isaac" 32 | elif number > 20 then "Fred" 33 | else "Sara" 34 | 35 | let resultOfInner = 36 | if someValue < 10.0 then innerFunction(5) 37 | else innerFunction(15) 38 | 39 | "Hello " + resultOfInner 40 | 41 | let result = sayHello(10.5) 42 | -------------------------------------------------------------------------------- /src/code-listings/lesson-06.fsx: -------------------------------------------------------------------------------- 1 | // Listing 6.1 2 | let name = "isaac" 3 | name = "kate" 4 | 5 | // Listing 6.2 6 | name <- "kate" 7 | 8 | // Listing 6.3 9 | let mutable name = "isaac" 10 | name <- "kate" 11 | 12 | // Listing 6.4 13 | open System.Windows.Forms 14 | let form = new Form() 15 | form.Show() 16 | form.Width <- 400 17 | form.Height <- 400 18 | form.Text <- "Hello from F#!" 19 | 20 | // Listing 6.5 21 | open System.Windows.Forms 22 | let form = new Form(Text = "Hello from F#!", Width = 300, Height = 300) 23 | form.Show() 24 | 25 | // Listing 6.6 26 | let mutable petrol = 100.0 27 | 28 | let drive(distance) = 29 | if distance = "far" then petrol <- petrol / 2.0 30 | elif distance = "medium" then petrol <- petrol - 10.0 31 | else petrol <- petrol - 1.0 32 | 33 | drive("far") 34 | drive("medium") 35 | drive("short") 36 | 37 | petrol 38 | 39 | // Listing 6.7 40 | let drive(petrol, distance) = 41 | if distance = "far" then petrol / 2.0 42 | elif distance = "medium" then petrol - 10.0 43 | else petrol - 1.0 44 | 45 | let petrol = 100.0 46 | let firstState = drive(petrol, "far") 47 | let secondState = drive(firstState, "medium") 48 | let finalState = drive(secondState, "short") 49 | 50 | // Now you try 51 | do 52 | let drive(petrol, distance) = 53 | if distance > 50 then petrol / 2.0 54 | elif distance > 25 then petrol - 10.0 55 | elif distance > 0 then petrol - 1. 56 | else petrol 57 | 58 | let petrol = 100.0 59 | let firstState = drive(petrol, 75) 60 | let secondState = drive(firstState, 40) 61 | let finalState = drive(secondState, 10) 62 | () 63 | -------------------------------------------------------------------------------- /src/code-listings/lesson-07.fsx: -------------------------------------------------------------------------------- 1 | // Listing 7.3 2 | open System 3 | 4 | let describeAge age = 5 | let ageDescription = 6 | if age < 18 then "Child!" 7 | elif age < 65 then "Adult!" 8 | else "OAP!" 9 | 10 | let greeting = "Hello" 11 | Console.WriteLine("{0}! You are a '{1}'.", greeting, ageDescription) 12 | 13 | // Listing 7.6 14 | let writeTextToDisk text = 15 | let path = System.IO.Path.GetTempFileName() 16 | System.IO.File.WriteAllText(path, text) 17 | path 18 | 19 | let createManyFiles() = 20 | writeTextToDisk "The quick brown fox jumped over the lazy dog" 21 | writeTextToDisk "The quick brown fox jumped over the lazy dog" 22 | writeTextToDisk "The quick brown fox jumped over the lazy dog" 23 | 24 | createManyFiles() 25 | 26 | // Listing 7.7 27 | let createManyFilesIgnore() = 28 | ignore(writeTextToDisk "The quick brown fox jumped over the lazy dog") 29 | ignore(writeTextToDisk "The quick brown fox jumped over the lazy dog") 30 | writeTextToDisk "The quick brown fox jumped over the lazy dog" 31 | 32 | // Listing 7.8 33 | let now = System.DateTime.UtcNow.TimeOfDay.TotalHours 34 | 35 | if now < 12.0 then Console.WriteLine "It's morning" 36 | elif now < 18.0 then Console.WriteLine "It's afternoon" 37 | elif now < 20.0 then ignore(5 + 5) 38 | else () -------------------------------------------------------------------------------- /src/code-listings/lesson-08/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/code-listings/lesson-08/Capstone1.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25420.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Capstone1", "Capstone1.fsproj", "{89D2850F-097B-4D17-9AC2-C08591A97F56}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {89D2850F-097B-4D17-9AC2-C08591A97F56}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {89D2850F-097B-4D17-9AC2-C08591A97F56}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {89D2850F-097B-4D17-9AC2-C08591A97F56}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {89D2850F-097B-4D17-9AC2-C08591A97F56}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /src/code-listings/lesson-08/Car - Solution.fs: -------------------------------------------------------------------------------- 1 | module Car 2 | 3 | open System 4 | 5 | /// Gets the distance to a given destination 6 | let getDistance (destination) = 7 | if destination = "Gas" then 10 8 | elif destination = "Stadium" then 25 9 | elif destination = "Office" then 50 10 | elif destination = "Home" then 25 11 | else failwith "Unknown destination!" 12 | 13 | /// Calculates remaining petrol after driving 14 | let calculateRemainingPetrol(currentPetrol, distance) = 15 | let remainingPetrol = currentPetrol - distance 16 | if remainingPetrol >= 0 then remainingPetrol 17 | else failwith "Ooops! You ran out of petrol!" 18 | 19 | /// Drives to a given destination given a starting amount of petrol 20 | let driveTo (petrol, destination) = 21 | let distanceToNextDestination = getDistance destination 22 | let petrolAfterDriving = calculateRemainingPetrol(petrol, distanceToNextDestination) 23 | if destination = "Gas" then petrolAfterDriving + 50 24 | else petrolAfterDriving -------------------------------------------------------------------------------- /src/code-listings/lesson-08/Car.fs: -------------------------------------------------------------------------------- 1 | module Car 2 | 3 | open System 4 | 5 | //TODO: Create helper functions to provide the building blocks to implement driveTo. 6 | 7 | /// Drives to a given destination given a starting amount of petrol 8 | let driveTo (petrol, destination) = petrol -------------------------------------------------------------------------------- /src/code-listings/lesson-08/Program.fs: -------------------------------------------------------------------------------- 1 | open System 2 | open Car 3 | 4 | let getDestination() = 5 | Console.Write("Enter destination: ") 6 | Console.ReadLine() 7 | 8 | let mutable petrol = 100 9 | 10 | [] 11 | let main argv = 12 | while true do 13 | try 14 | let destination = getDestination() 15 | printfn "Trying to drive to %s" destination 16 | petrol <- driveTo(petrol, destination) 17 | printfn "Made it to %s! You have %d petrol left" destination petrol 18 | with ex -> printfn "ERROR: %s" ex.Message 19 | 0 -------------------------------------------------------------------------------- /src/code-listings/lesson-08/paket.references: -------------------------------------------------------------------------------- 1 | FSharp.Core -------------------------------------------------------------------------------- /src/code-listings/lesson-08/scratchpad.fsx: -------------------------------------------------------------------------------- 1 | open System 2 | 3 | /// Gets the distance to a given destination 4 | let getDistance (destination) = 5 | if destination = "Gas" then 10 6 | // fill in the blanks! 7 | else failwith "Unknown destination!" 8 | 9 | // Couple of quick tests 10 | getDistance("Home") = 25 11 | getDistance("Stadium") = 25 12 | 13 | -------------------------------------------------------------------------------- /src/code-listings/lesson-09.fsx: -------------------------------------------------------------------------------- 1 | (* Note the listings below make use of the "do" keyword. These simply allow us to declare 2 | an abitrary scope so that we can re-use symbols e.g. "name" without getting warnings from the 3 | compiler. This is only required for symbols declared directly within scripts. *) 4 | 5 | // Listing 9.3 6 | do 7 | let parseName(name:string) = 8 | let parts = name.Split(' ') 9 | let forename = parts.[0] 10 | let surname = parts.[1] 11 | forename, surname 12 | let name = parseName("Isaac Abraham") 13 | let forename, surname = name 14 | 15 | let fname, sname = parseName("Isaac Abraham") 16 | () 17 | 18 | // Now you try 19 | let parse (person:string) = 20 | let parts = person.Split(' ') 21 | let age = int parts.[2] 22 | parts.[0], parts.[1], age 23 | 24 | let fname, sname, age = parse "Mary Simpson 24" 25 | 26 | // Listing 9.4 27 | do 28 | let nameAndAge = ("Joe", "Bloggs"), 28 29 | let name, age = nameAndAge 30 | let (forename, surname), theAge = nameAndAge 31 | () 32 | 33 | // Listing 9.5 34 | do 35 | let nameAndAge = "Jane", "Smith", 25 36 | let forename, surname, _ = nameAndAge 37 | () 38 | 39 | // Listing 9.6 40 | let explicit : int * int = 10,5 41 | let implicit = 10,5 42 | 43 | let addNumbers arguments = 44 | let a, b = arguments 45 | a + b 46 | 47 | // Listing 9.7 48 | let addNumbersGeneric arguments = 49 | let a, b, c, _ = arguments 50 | a + b 51 | 52 | -------------------------------------------------------------------------------- /src/code-listings/lesson-10.fsx: -------------------------------------------------------------------------------- 1 | // Listing 10.4 2 | 3 | type Address = 4 | { Street : string 5 | Town : string 6 | City : string } 7 | 8 | // Listing 10.5 9 | type Customer = 10 | { Forename : string 11 | Surname : string 12 | Age : int 13 | Address : Address 14 | EmailAddress : string } 15 | 16 | let customer = 17 | { Forename = "Joe" 18 | Surname = "Bloggs" 19 | Age = 30 20 | Address = 21 | { Street = "The Street" 22 | Town = "The Town" 23 | City = "The City" } 24 | EmailAddress = "joe@bloggs.com" } 25 | 26 | // Listing 10.6 27 | let address : Address = 28 | { Street = "The Street" 29 | Town = "The Town" 30 | City = "The City" } 31 | 32 | let addressExplicit = 33 | { Address.Street = "The Street" 34 | Town = "The Town" 35 | City = "The City" } 36 | 37 | // Listing 10.7 38 | let updatedCustomer = { customer with Age = 31; EmailAddress = "joe@bloggs.co.uk" } 39 | 40 | // Listing 10.8 41 | let isSameAddress = (address = addressExplicit) 42 | 43 | // Now you try 44 | let updateAge customer = 45 | let newAge = 46 | let r = System.Random() 47 | r.Next(18, 46) 48 | printfn "Changed customer's age from %d to %d" customer.Age newAge 49 | { customer with Age = newAge } 50 | 51 | let randomAgeCustomer = updateAge customer 52 | 53 | // Listing 10.9 54 | do 55 | let myHome = { Street = "The Street"; Town = "The Town"; City = "The City" } 56 | let myHome = { myHome with City = "The Other City" } 57 | let myHome = { myHome with City = "The Third City" } 58 | () 59 | -------------------------------------------------------------------------------- /src/code-listings/lesson-12/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/code-listings/lesson-12/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | namespace modules_and_namespaces.AssemblyInfo 2 | 3 | open System.Reflection 4 | open System.Runtime.CompilerServices 5 | open System.Runtime.InteropServices 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [] 11 | [] 12 | [] 13 | [] 14 | [] 15 | [] 16 | [] 17 | [] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [] 23 | 24 | // The following GUID is for the ID of the typelib if this project is exposed to COM 25 | [] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [] 37 | [] 38 | [] 39 | 40 | do 41 | () -------------------------------------------------------------------------------- /src/code-listings/lesson-12/Domain.fs: -------------------------------------------------------------------------------- 1 | namespace Domain 2 | 3 | type Customer = 4 | { FirstName : string 5 | LastName : string 6 | Age : int } 7 | -------------------------------------------------------------------------------- /src/code-listings/lesson-12/Operations.fs: -------------------------------------------------------------------------------- 1 | module Operations 2 | 3 | open Domain 4 | 5 | let getInitials customer = customer.FirstName.[0], customer.LastName.[0] 6 | let isOlderThan age customer = customer.Age > age 7 | -------------------------------------------------------------------------------- /src/code-listings/lesson-12/Program.fs: -------------------------------------------------------------------------------- 1 | open Domain 2 | open Operations 3 | 4 | [] 5 | let main argv = 6 | let joe = { FirstName = "joe"; LastName = "bloggs"; Age = 21 } 7 | if joe |> isOlderThan 18 then printfn "%s is an adult!" joe.FirstName 8 | else printfn "%s is a child." joe.FirstName 9 | 10 | 0 // return an integer exit code 11 | -------------------------------------------------------------------------------- /src/code-listings/lesson-12/lesson-12.fsx: -------------------------------------------------------------------------------- 1 | // Listing 12.1 2 | let file = @"C:\users\isaac\downloads\foo.txt" 3 | System.IO.File.ReadLines file 4 | 5 | open System.IO 6 | File.ReadAllLines file 7 | 8 | System.IO.File.Delete 9 | -------------------------------------------------------------------------------- /src/code-listings/lesson-12/modules-and-namespaces.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.24720.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "modules-and-namespaces", "modules-and-namespaces.fsproj", "{DF445664-8ABC-4029-8D67-F3B9B29088AA}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {DF445664-8ABC-4029-8D67-F3B9B29088AA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {DF445664-8ABC-4029-8D67-F3B9B29088AA}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {DF445664-8ABC-4029-8D67-F3B9B29088AA}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {DF445664-8ABC-4029-8D67-F3B9B29088AA}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /src/code-listings/lesson-13.fsx: -------------------------------------------------------------------------------- 1 | // Listing 13.5 2 | type Customer = { Age : int } 3 | let where filter customers = 4 | seq { 5 | for customer in customers do 6 | if filter customer then 7 | yield customer } 8 | 9 | let customers = [ { Age = 21 }; { Age = 35 }; { Age = 36 } ] 10 | 11 | let whereCustomersAreOver35 customers = 12 | seq { 13 | for customer in customers do 14 | if customer.Age > 35 then 15 | yield customer } 16 | 17 | 18 | let isOver35 customer = customer.Age > 35 19 | 20 | customers |> where isOver35 21 | customers |> where (fun customer -> customer.Age > 35) 22 | 23 | 24 | // Listing 13.6 25 | let printCustomerAge writer customer = 26 | if customer.Age < 13 then writer "Child!" 27 | elif customer.Age < 20 then writer "Teenager!" 28 | else writer "Adult!" 29 | 30 | // Listing 13.7 31 | open System 32 | printCustomerAge Console.WriteLine { Age = 21 } 33 | 34 | let printToConsole = printCustomerAge Console.WriteLine 35 | printToConsole { Age = 21 } 36 | printToConsole { Age = 12 } 37 | printToConsole { Age = 18 } 38 | 39 | // Listing 13.8 40 | open System.IO 41 | let writeToFile text = File.WriteAllText(@"C:\temp\output.txt", text) 42 | 43 | let printToFile = printCustomerAge writeToFile 44 | printToFile { Age = 21 } 45 | 46 | let contentsFromDisk = File.ReadAllText @"C:\temp\output.txt" 47 | 48 | -------------------------------------------------------------------------------- /src/code-listings/lesson-14/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/code-listings/lesson-14/Auditing.fs: -------------------------------------------------------------------------------- 1 | module Capstone2.Auditing 2 | 3 | open Capstone2.Domain 4 | open System.IO 5 | 6 | /// Logs to the console 7 | let console account message = printfn "Account %O: %s" account.AccountId message 8 | 9 | /// Logs to the file system 10 | let fileSystem account message = 11 | Directory.CreateDirectory(sprintf @"C:\temp\learnfsharp\lesson14\%s" account.Owner.Name) |> ignore 12 | let filePath = sprintf @"C:\temp\learnfsharp\lesson14\%s\%O.txt" account.Owner.Name account.AccountId 13 | File.AppendAllLines(filePath, [ message ]) -------------------------------------------------------------------------------- /src/code-listings/lesson-14/Capstone2.fsx: -------------------------------------------------------------------------------- 1 | #load "Domain.fs" 2 | #load "Operations.fs" 3 | #load "Auditing.fs" 4 | 5 | open Capstone2.Operations 6 | open Capstone2.Domain 7 | open Capstone2.Auditing 8 | open System 9 | 10 | // Create console-auditing withdraw and deposit functions. 11 | // Notice that the signatures of the new "shadowed" functions 12 | // have the same signature as the original ones. This is equivalent 13 | // to a Decorator in OO terms. 14 | let withdraw = auditAs "withdraw" console withdraw 15 | let deposit = auditAs "deposit" console deposit 16 | 17 | let customer = { Name = "Isaac" } 18 | let account = { AccountId = Guid.Empty; Owner = customer; Balance = 90M } 19 | 20 | // Test out withdraw 21 | let newAccount = account |> withdraw 10M 22 | newAccount.Balance = 80M // should be true! 23 | 24 | // Test out console auditer 25 | console account "Testing console audit" 26 | // should print "Account 00000000-0000-0000-0000-000000000000: Testing console audit" 27 | 28 | account 29 | |> withdraw 50M 30 | |> deposit 50M 31 | |> deposit 100M 32 | |> withdraw 50M 33 | |> withdraw 350M -------------------------------------------------------------------------------- /src/code-listings/lesson-14/Capstone2.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.24720.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Capstone2", "Capstone2.fsproj", "{5194054C-0572-4E34-9D53-9748B6641595}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {5194054C-0572-4E34-9D53-9748B6641595}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {5194054C-0572-4E34-9D53-9748B6641595}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {5194054C-0572-4E34-9D53-9748B6641595}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {5194054C-0572-4E34-9D53-9748B6641595}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /src/code-listings/lesson-14/Domain.fs: -------------------------------------------------------------------------------- 1 | namespace Capstone2.Domain 2 | 3 | type Customer = { Name : string } 4 | type Account = { AccountId : System.Guid; Owner : Customer; Balance : decimal } -------------------------------------------------------------------------------- /src/code-listings/lesson-14/Operations.fs: -------------------------------------------------------------------------------- 1 | module Capstone2.Operations 2 | 3 | open System 4 | open Capstone2.Domain 5 | 6 | /// Withdraws an amount of an account (if there are sufficient funds) 7 | let withdraw amount account = 8 | if amount > account.Balance then account 9 | else { account with Balance = account.Balance - amount } 10 | 11 | /// Deposits an amount into an account 12 | let deposit amount account = 13 | { account with Balance = account.Balance + amount } 14 | 15 | /// Runs some account operation such as withdraw or deposit with auditing. 16 | let auditAs operationName audit operation amount account = 17 | audit account (sprintf "%O: Performing a %s operation for £%M..." DateTime.UtcNow operationName amount) 18 | let updatedAccount = operation amount account 19 | 20 | let accountIsUnchanged = (updatedAccount = account) 21 | 22 | if accountIsUnchanged then audit account (sprintf "%O: Transaction rejected!" DateTime.UtcNow) 23 | else audit account (sprintf "%O: Transaction accepted! Balance is now £%M." DateTime.UtcNow updatedAccount.Balance) 24 | 25 | updatedAccount -------------------------------------------------------------------------------- /src/code-listings/lesson-14/Program.fs: -------------------------------------------------------------------------------- 1 | module Capstone2.Program 2 | 3 | open System 4 | open Capstone2.Domain 5 | open Capstone2.Operations 6 | 7 | [] 8 | let main argv = 9 | let mutable account = 10 | let customer = 11 | Console.Write "Please enter your name: " 12 | let customerName = Console.ReadLine() 13 | { Name = customerName } 14 | 15 | Console.Write "Enter opening balance: " 16 | let balance = Console.ReadLine() |> Decimal.Parse 17 | { AccountId = Guid.NewGuid() 18 | Owner = customer 19 | Balance = balance } 20 | 21 | let withdrawWithAudit = withdraw |> auditAs "withdraw" Auditing.fileSystem 22 | let depositWithAudit = deposit |> auditAs "deposit" Auditing.fileSystem 23 | 24 | while true do 25 | let action = 26 | Console.WriteLine() 27 | printfn "Current balance is £%M" account.Balance 28 | Console.Write "(d)eposit, (w)ithdraw or e(x)it: " 29 | Console.ReadLine() 30 | 31 | if action = "x" then Environment.Exit 0 32 | 33 | let amount = 34 | Console.Write "Amount: " 35 | Console.ReadLine() |> Decimal.Parse 36 | 37 | // Mutate account value via an expression 38 | account <- 39 | if action = "d" then account |> depositWithAudit amount 40 | elif action = "w" then account |> withdrawWithAudit amount 41 | else account 42 | 43 | 0 // return an integer exit code -------------------------------------------------------------------------------- /src/code-listings/lesson-16.fsx: -------------------------------------------------------------------------------- 1 | // Listing 16.1 2 | let numbers = [ 1 .. 10 ] 3 | let timesTwo n = n * 2 4 | 5 | let outputImperative = ResizeArray() 6 | for number in numbers do 7 | outputImperative.Add (number |> timesTwo) 8 | 9 | let outputFunctional = numbers |> List.map timesTwo 10 | 11 | // Listing 16.2 12 | type Order = { OrderId : int } 13 | type Customer = { CustomerId : int; Orders : Order list; Town : string } 14 | let customers : Customer list = [] 15 | let orders : Order list = customers |> List.collect(fun c -> c.Orders) 16 | 17 | // Listing 16.3 18 | open System 19 | 20 | [ DateTime(2010,5,1); DateTime(2010,6,1); DateTime(2010,6,12); DateTime(2010,7,3) ] 21 | |> List.pairwise 22 | |> List.map(fun (a, b) -> b - a) 23 | |> List.map(fun time -> time.TotalDays) 24 | 25 | // Listing 16.4 26 | let londonCustomers, otherCustomers = 27 | customers 28 | |> List.partition(fun c -> c.Town = "London") 29 | 30 | // Listing 16.5 31 | do 32 | let numbers = [ 1.0 .. 10.0 ] 33 | let total = numbers |> List.sum 34 | let average = numbers |> List.average 35 | let max = numbers |> List.max 36 | let min = numbers |> List.min 37 | () 38 | 39 | // Listing 16.6 40 | let numberOne = 41 | [ 1 .. 5 ] 42 | |> List.toArray 43 | |> Seq.ofArray 44 | |> Seq.head 45 | -------------------------------------------------------------------------------- /src/code-listings/lesson-19/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/code-listings/lesson-19/Auditing.fs: -------------------------------------------------------------------------------- 1 | module Capstone3.Auditing 2 | 3 | open Capstone3.Operations 4 | open Capstone3.Domain 5 | 6 | /// Logs to the console 7 | let printTransaction _ accountId message = printfn "Account %O: %s" accountId message 8 | 9 | // Logs to both console and file system 10 | let composedLogger = 11 | let loggers = 12 | [ FileRepository.writeTransaction 13 | printTransaction ] 14 | fun accountId owner message -> 15 | loggers 16 | |> List.iter(fun logger -> logger accountId owner message) -------------------------------------------------------------------------------- /src/code-listings/lesson-19/Capstone3.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.24720.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Capstone3", "Capstone3.fsproj", "{5194054C-0572-4E34-9D53-9748B6641595}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {5194054C-0572-4E34-9D53-9748B6641595}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {5194054C-0572-4E34-9D53-9748B6641595}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {5194054C-0572-4E34-9D53-9748B6641595}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {5194054C-0572-4E34-9D53-9748B6641595}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /src/code-listings/lesson-19/Domain.fs: -------------------------------------------------------------------------------- 1 | namespace Capstone3.Domain 2 | 3 | open System 4 | 5 | type Customer = { Name : string } 6 | type Account = { AccountId : Guid; Owner : Customer; Balance : decimal } -------------------------------------------------------------------------------- /src/code-listings/lesson-19/FileRepository.fs: -------------------------------------------------------------------------------- 1 | module Capstone3.FileRepository 2 | 3 | open Capstone3.Domain 4 | open System.IO 5 | open System 6 | 7 | let private accountsPath = 8 | let path = @"accounts" 9 | Directory.CreateDirectory path |> ignore 10 | path 11 | let private findAccountFolder owner = 12 | let folders = Directory.EnumerateDirectories(accountsPath, sprintf "%s_*" owner) 13 | if Seq.isEmpty folders then "" 14 | else 15 | let folder = Seq.head folders 16 | DirectoryInfo(folder).Name 17 | let private buildPath(owner, accountId:Guid) = sprintf @"%s\%s_%O" accountsPath owner accountId 18 | 19 | /// Logs to the file system 20 | let writeTransaction accountId owner message = 21 | let path = buildPath(owner, accountId) 22 | path |> Directory.CreateDirectory |> ignore 23 | let filePath = sprintf "%s/%d.txt" path (DateTime.UtcNow.ToFileTimeUtc()) 24 | File.WriteAllText(filePath, message) -------------------------------------------------------------------------------- /src/code-listings/lesson-19/Operations.fs: -------------------------------------------------------------------------------- 1 | module Capstone3.Operations 2 | 3 | open System 4 | open Capstone3.Domain 5 | 6 | /// Withdraws an amount of an account (if there are sufficient funds) 7 | let withdraw amount account = 8 | if amount > account.Balance then account 9 | else { account with Balance = account.Balance - amount } 10 | 11 | /// Deposits an amount into an account 12 | let deposit amount account = 13 | { account with Balance = account.Balance + amount } 14 | 15 | /// Runs some account operation such as withdraw or deposit with auditing. 16 | let auditAs operationName audit operation amount account = 17 | let audit = audit account.AccountId account.Owner.Name 18 | audit (sprintf "%O: Performing a %s operation for £%M..." DateTime.UtcNow operationName amount) 19 | let updatedAccount = operation amount account 20 | 21 | let accountIsUnchanged = (updatedAccount = account) 22 | 23 | if accountIsUnchanged then audit (sprintf "%O: Transaction rejected!" DateTime.UtcNow) 24 | else audit (sprintf "%O: Transaction accepted! Balance is now £%M." DateTime.UtcNow updatedAccount.Balance) 25 | 26 | updatedAccount -------------------------------------------------------------------------------- /src/code-listings/lesson-19/Program.fs: -------------------------------------------------------------------------------- 1 | module Capstone3.Program 2 | 3 | open System 4 | open Capstone3.Domain 5 | open Capstone3.Operations 6 | 7 | [] 8 | let main _ = 9 | let name = 10 | Console.Write "Please enter your name: " 11 | Console.ReadLine() 12 | 13 | let withdrawWithAudit = auditAs "withdraw" Auditing.composedLogger withdraw 14 | let depositWithAudit = auditAs "deposit" Auditing.composedLogger deposit 15 | 16 | let openingAccount = { Owner = { Name = name }; Balance = 0M; AccountId = Guid.Empty } 17 | 18 | let closingAccount = 19 | // Fill in the main loop here... 20 | openingAccount 21 | 22 | Console.Clear() 23 | printfn "Closing Balance:\r\n %A" closingAccount 24 | Console.ReadKey() |> ignore 25 | 26 | 0 -------------------------------------------------------------------------------- /src/code-listings/lesson-19/Scratchpad.fsx: -------------------------------------------------------------------------------- 1 | #load "Domain.fs" 2 | #load "Operations.fs" 3 | 4 | open Capstone3.Operations 5 | open Capstone3.Domain 6 | open System 7 | 8 | -------------------------------------------------------------------------------- /src/code-listings/lesson-19/sample-solution/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/code-listings/lesson-19/sample-solution/Auditing.fs: -------------------------------------------------------------------------------- 1 | module Capstone3.Auditing 2 | 3 | open Capstone3.Operations 4 | open Capstone3.Domain 5 | 6 | /// Logs to the console 7 | let printTransaction _ accountId transaction = 8 | printfn "Account %O: %s of %M (approved: %b)" accountId transaction.Operation transaction.Amount transaction.Accepted 9 | 10 | // Logs to both console and file system 11 | let composedLogger = 12 | let loggers = 13 | [ FileRepository.writeTransaction 14 | printTransaction ] 15 | fun accountId owner transaction -> 16 | loggers 17 | |> List.iter(fun logger -> logger accountId owner transaction) -------------------------------------------------------------------------------- /src/code-listings/lesson-19/sample-solution/Capstone3.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.24720.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Capstone3", "Capstone3.fsproj", "{5194054C-0572-4E34-9D53-9748B6641595}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {5194054C-0572-4E34-9D53-9748B6641595}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {5194054C-0572-4E34-9D53-9748B6641595}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {5194054C-0572-4E34-9D53-9748B6641595}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {5194054C-0572-4E34-9D53-9748B6641595}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /src/code-listings/lesson-19/sample-solution/Domain.fs: -------------------------------------------------------------------------------- 1 | namespace Capstone3.Domain 2 | 3 | open System 4 | 5 | type Customer = { Name : string } 6 | type Account = { AccountId : Guid; Owner : Customer; Balance : decimal } 7 | type Transaction = { Timestamp : DateTime; Operation : string; Amount : decimal; Accepted : bool } 8 | 9 | module Transactions = 10 | /// Serializes a transaction 11 | let serialize transaction = 12 | sprintf "%O***%s***%M***%b" transaction.Timestamp transaction.Operation transaction.Amount transaction.Accepted 13 | 14 | /// Deserializes a transaction 15 | let deserialize (fileContents:string) = 16 | let parts = fileContents.Split([|"***"|], StringSplitOptions.None) 17 | { Timestamp = DateTime.Parse parts.[0] 18 | Operation = parts.[1] 19 | Amount = Decimal.Parse parts.[2] 20 | Accepted = Boolean.Parse parts.[3] } -------------------------------------------------------------------------------- /src/code-listings/lesson-19/sample-solution/FileRepository.fs: -------------------------------------------------------------------------------- 1 | module Capstone3.FileRepository 2 | 3 | open Capstone3.Domain 4 | open System.IO 5 | open System 6 | 7 | let private accountsPath = 8 | let path = @"accounts" 9 | Directory.CreateDirectory path |> ignore 10 | path 11 | let private findAccountFolder owner = 12 | let folders = Directory.EnumerateDirectories(accountsPath, sprintf "%s_*" owner) 13 | if Seq.isEmpty folders then "" 14 | else 15 | let folder = Seq.head folders 16 | DirectoryInfo(folder).Name 17 | let private buildPath(owner, accountId:Guid) = sprintf @"%s\%s_%O" accountsPath owner accountId 18 | 19 | let loadTransactions (folder:string) = 20 | let owner, accountId = 21 | let parts = folder.Split '_' 22 | parts.[0], Guid.Parse parts.[1] 23 | owner, accountId, buildPath(owner, accountId) 24 | |> Directory.EnumerateFiles 25 | |> Seq.map (File.ReadAllText >> Transactions.deserialize) 26 | 27 | /// Finds all transactions from disk for specific owner. 28 | let findTransactionsOnDisk owner = 29 | let folder = findAccountFolder owner 30 | if String.IsNullOrEmpty folder then owner, Guid.NewGuid(), Seq.empty 31 | else loadTransactions folder 32 | 33 | /// Logs to the file system 34 | let writeTransaction accountId owner transaction = 35 | let path = buildPath(owner, accountId) 36 | path |> Directory.CreateDirectory |> ignore 37 | let filePath = sprintf "%s/%d.txt" path (transaction.Timestamp.ToFileTimeUtc()) 38 | let line = sprintf "%O***%s***%M***%b" transaction.Timestamp transaction.Operation transaction.Amount transaction.Accepted 39 | File.WriteAllText(filePath, line) -------------------------------------------------------------------------------- /src/code-listings/lesson-19/sample-solution/Operations.fs: -------------------------------------------------------------------------------- 1 | module Capstone3.Operations 2 | 3 | open System 4 | open Capstone3.Domain 5 | 6 | /// Withdraws an amount of an account (if there are sufficient funds) 7 | let withdraw amount account = 8 | if amount > account.Balance then account 9 | else { account with Balance = account.Balance - amount } 10 | 11 | /// Deposits an amount into an account 12 | let deposit amount account = 13 | { account with Balance = account.Balance + amount } 14 | 15 | /// Runs some account operation such as withdraw or deposit with auditing. 16 | let auditAs operationName audit operation amount account = 17 | let updatedAccount = operation amount account 18 | 19 | let accountIsUnchanged = (updatedAccount = account) 20 | 21 | let transaction = 22 | let transaction = { Operation = operationName; Amount = amount; Timestamp = DateTime.UtcNow; Accepted = true } 23 | if accountIsUnchanged then { transaction with Accepted = false } 24 | else transaction 25 | 26 | audit account.AccountId account.Owner.Name transaction 27 | updatedAccount 28 | 29 | /// Creates an account from a historical set of transactions 30 | let loadAccount (owner, accountId, transactions) = 31 | let openingAccount = { AccountId = accountId; Balance = 0M; Owner = { Name = owner } } 32 | 33 | transactions 34 | |> Seq.sortBy(fun txn -> txn.Timestamp) 35 | |> Seq.fold(fun account txn -> 36 | if txn.Operation = "withdraw" then account |> withdraw txn.Amount 37 | else account |> deposit txn.Amount) openingAccount -------------------------------------------------------------------------------- /src/code-listings/lesson-23-1.fsx: -------------------------------------------------------------------------------- 1 | // Listing 23.1 2 | type Customer = 3 | { CustomerId : string 4 | Email : string 5 | Telephone : string 6 | Address : string } 7 | 8 | // Listing 23.2 9 | let createCustomer customerId email telephone address = 10 | { CustomerId = telephone 11 | Email = customerId 12 | Telephone = address 13 | Address = email } 14 | let customer = createCustomer "C-123" "nicki@myemail.com" "029-293-23" "1 The Street" 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/code-listings/lesson-23-2.fsx: -------------------------------------------------------------------------------- 1 | // Listing 23.3 2 | type Address = Address of string 3 | let myAddress = Address "1 The Street" 4 | let isTheSameAddress = (myAddress = "1 The Street") 5 | let (Address addressData) = myAddress 6 | 7 | // Now you try #1 8 | type CustomerId = CustomerId of string 9 | type Email = Email of string 10 | type Telephone = Telephone of string 11 | type Customer = 12 | { CustomerId : CustomerId 13 | Email : Email 14 | Telephone : Telephone 15 | Address : Address } 16 | 17 | let createCustomer customerId email telephone address = 18 | { CustomerId = customerId 19 | Email = email 20 | Telephone = telephone 21 | Address = address } 22 | let customer = 23 | createCustomer 24 | (CustomerId "C-123") 25 | (Email "nicki@myemail.com") 26 | (Telephone "029-293-23") 27 | (Address "1 The Street") -------------------------------------------------------------------------------- /src/code-listings/lesson-24/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/code-listings/lesson-24/Auditing.fs: -------------------------------------------------------------------------------- 1 | module Capstone4.Auditing 2 | 3 | open Capstone4.Operations 4 | open Capstone4.Domain 5 | 6 | /// Logs to the console 7 | let printTransaction _ accountId transaction = 8 | printfn "Account %O: %s of %M (approved: %b)" accountId transaction.Operation transaction.Amount transaction.Accepted 9 | 10 | // Logs to both console and file system 11 | let composedLogger = 12 | let loggers = 13 | [ FileRepository.writeTransaction 14 | printTransaction ] 15 | fun accountId owner transaction -> 16 | loggers 17 | |> List.iter(fun logger -> logger accountId owner transaction) -------------------------------------------------------------------------------- /src/code-listings/lesson-24/Capstone4.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.24720.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Capstone4", "Capstone4.fsproj", "{5194054C-0572-4E34-9D53-9748B6641595}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {5194054C-0572-4E34-9D53-9748B6641595}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {5194054C-0572-4E34-9D53-9748B6641595}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {5194054C-0572-4E34-9D53-9748B6641595}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {5194054C-0572-4E34-9D53-9748B6641595}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /src/code-listings/lesson-24/Domain.fs: -------------------------------------------------------------------------------- 1 | namespace Capstone4.Domain 2 | 3 | open System 4 | 5 | type Customer = { Name : string } 6 | type Account = { AccountId : Guid; Owner : Customer; Balance : decimal } 7 | type Transaction = { Timestamp : DateTime; Operation : string; Amount : decimal; Accepted : bool } 8 | 9 | module Transactions = 10 | /// Serializes a transaction 11 | let serialize transaction = 12 | sprintf "%O***%s***%M***%b" transaction.Timestamp transaction.Operation transaction.Amount transaction.Accepted 13 | 14 | /// Deserializes a transaction 15 | let deserialize (fileContents:string) = 16 | let parts = fileContents.Split([|"***"|], StringSplitOptions.None) 17 | { Timestamp = DateTime.Parse parts.[0] 18 | Operation = parts.[1] 19 | Amount = Decimal.Parse parts.[2] 20 | Accepted = Boolean.Parse parts.[3] } -------------------------------------------------------------------------------- /src/code-listings/lesson-24/FileRepository.fs: -------------------------------------------------------------------------------- 1 | module Capstone4.FileRepository 2 | 3 | open Capstone4.Domain 4 | open System.IO 5 | open System 6 | 7 | let private accountsPath = 8 | let path = @"accounts" 9 | Directory.CreateDirectory path |> ignore 10 | path 11 | let private findAccountFolder owner = 12 | let folders = Directory.EnumerateDirectories(accountsPath, sprintf "%s_*" owner) 13 | if Seq.isEmpty folders then "" 14 | else 15 | let folder = Seq.head folders 16 | DirectoryInfo(folder).Name 17 | let private buildPath(owner, accountId:Guid) = sprintf @"%s\%s_%O" accountsPath owner accountId 18 | 19 | let loadTransactions (folder:string) = 20 | let owner, accountId = 21 | let parts = folder.Split '_' 22 | parts.[0], Guid.Parse parts.[1] 23 | owner, accountId, buildPath(owner, accountId) 24 | |> Directory.EnumerateFiles 25 | |> Seq.map (File.ReadAllText >> Transactions.deserialize) 26 | 27 | /// Finds all transactions from disk for specific owner. 28 | let findTransactionsOnDisk owner = 29 | let folder = findAccountFolder owner 30 | if String.IsNullOrEmpty folder then owner, Guid.NewGuid(), Seq.empty 31 | else loadTransactions folder 32 | 33 | /// Logs to the file system 34 | let writeTransaction accountId owner transaction = 35 | let path = buildPath(owner, accountId) 36 | path |> Directory.CreateDirectory |> ignore 37 | let filePath = sprintf "%s/%d.txt" path (transaction.Timestamp.ToFileTimeUtc()) 38 | let line = sprintf "%O***%s***%M***%b" transaction.Timestamp transaction.Operation transaction.Amount transaction.Accepted 39 | File.WriteAllText(filePath, line) -------------------------------------------------------------------------------- /src/code-listings/lesson-24/Operations.fs: -------------------------------------------------------------------------------- 1 | module Capstone4.Operations 2 | 3 | open System 4 | open Capstone4.Domain 5 | 6 | /// Withdraws an amount of an account (if there are sufficient funds) 7 | let withdraw amount account = 8 | if amount > account.Balance then account 9 | else { account with Balance = account.Balance - amount } 10 | 11 | /// Deposits an amount into an account 12 | let deposit amount account = 13 | { account with Balance = account.Balance + amount } 14 | 15 | /// Runs some account operation such as withdraw or deposit with auditing. 16 | let auditAs operationName audit operation amount account = 17 | let updatedAccount = operation amount account 18 | 19 | let accountIsUnchanged = (updatedAccount = account) 20 | 21 | let transaction = 22 | let transaction = { Operation = operationName; Amount = amount; Timestamp = DateTime.UtcNow; Accepted = true } 23 | if accountIsUnchanged then { transaction with Accepted = false } 24 | else transaction 25 | 26 | audit account.AccountId account.Owner.Name transaction 27 | updatedAccount 28 | 29 | /// Creates an account from a historical set of transactions 30 | let loadAccount (owner, accountId, transactions) = 31 | let openingAccount = { AccountId = accountId; Balance = 0M; Owner = { Name = owner } } 32 | 33 | transactions 34 | |> Seq.sortBy(fun txn -> txn.Timestamp) 35 | |> Seq.fold(fun account txn -> 36 | if txn.Operation = "withdraw" then account |> withdraw txn.Amount 37 | else account |> deposit txn.Amount) openingAccount -------------------------------------------------------------------------------- /src/code-listings/lesson-24/Scratchpad.fsx: -------------------------------------------------------------------------------- 1 | #load "Domain.fs" 2 | #load "Operations.fs" 3 | 4 | open Capstone4.Operations 5 | open Capstone4.Domain 6 | open System 7 | -------------------------------------------------------------------------------- /src/code-listings/lesson-24/sample-solution/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/code-listings/lesson-24/sample-solution/Auditing.fs: -------------------------------------------------------------------------------- 1 | module Capstone4.Auditing 2 | 3 | open Capstone4.Domain 4 | 5 | /// Logs to the console 6 | let printTransaction _ accountId transaction = 7 | printfn "Account %O: %s of %M" accountId transaction.Operation transaction.Amount 8 | 9 | // Logs to both console and file system 10 | let composedLogger = 11 | let loggers = 12 | [ FileRepository.writeTransaction 13 | printTransaction ] 14 | fun accountId owner transaction -> 15 | loggers 16 | |> List.iter(fun logger -> logger accountId owner transaction) -------------------------------------------------------------------------------- /src/code-listings/lesson-24/sample-solution/Capstone4.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.24720.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Capstone4", "Capstone4.fsproj", "{5194054C-0572-4E34-9D53-9748B6641595}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {5194054C-0572-4E34-9D53-9748B6641595}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {5194054C-0572-4E34-9D53-9748B6641595}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {5194054C-0572-4E34-9D53-9748B6641595}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {5194054C-0572-4E34-9D53-9748B6641595}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /src/code-listings/lesson-24/sample-solution/Domain.fs: -------------------------------------------------------------------------------- 1 | namespace Capstone4.Domain 2 | 3 | open System 4 | 5 | type BankOperation = Deposit | Withdraw 6 | type Customer = { Name : string } 7 | type Account = { AccountId : Guid; Owner : Customer; Balance : decimal } 8 | type Transaction = { Timestamp : DateTime; Operation : string; Amount : decimal } 9 | 10 | /// Represents a bank account that is known to be in credit. 11 | type CreditAccount = CreditAccount of Account 12 | /// A bank account which can either be in credit or overdrawn. 13 | type RatedAccount = 14 | | InCredit of CreditAccount 15 | | Overdrawn of Account 16 | member this.GetField getter = 17 | match this with 18 | | InCredit (CreditAccount account) -> getter account 19 | | Overdrawn account -> getter account 20 | 21 | module Transactions = 22 | /// Serializes a transaction 23 | let serialize transaction = 24 | sprintf "%O***%s***%M" transaction.Timestamp transaction.Operation transaction.Amount 25 | 26 | /// Deserializes a transaction 27 | let deserialize (fileContents:string) = 28 | let parts = fileContents.Split([|"***"|], StringSplitOptions.None) 29 | { Timestamp = DateTime.Parse parts.[0] 30 | Operation = parts.[1] 31 | Amount = Decimal.Parse parts.[2] } -------------------------------------------------------------------------------- /src/code-listings/lesson-24/sample-solution/FileRepository.fs: -------------------------------------------------------------------------------- 1 | module Capstone4.FileRepository 2 | 3 | open Capstone4.Domain 4 | open System.IO 5 | open System 6 | 7 | let private accountsPath = 8 | let path = @"accounts" 9 | Directory.CreateDirectory path |> ignore 10 | path 11 | let private tryFindAccountFolder owner = 12 | let folders = Directory.EnumerateDirectories(accountsPath, sprintf "%s_*" owner) |> Seq.toList 13 | match folders with 14 | | [] -> None 15 | | folder :: _ -> Some(DirectoryInfo(folder).Name) 16 | 17 | let private buildPath(owner, accountId:Guid) = sprintf @"%s\%s_%O" accountsPath owner accountId 18 | 19 | let loadTransactions (folder:string) = 20 | let owner, accountId = 21 | let parts = folder.Split '_' 22 | parts.[0], Guid.Parse parts.[1] 23 | owner, accountId, buildPath(owner, accountId) 24 | |> Directory.EnumerateFiles 25 | |> Seq.map (File.ReadAllText >> Transactions.deserialize) 26 | 27 | /// Finds all transactions from disk for specific owner. 28 | let tryFindTransactionsOnDisk = tryFindAccountFolder >> Option.map loadTransactions 29 | 30 | /// Logs to the file system 31 | let writeTransaction accountId owner transaction = 32 | let path = buildPath(owner, accountId) 33 | path |> Directory.CreateDirectory |> ignore 34 | let filePath = sprintf "%s/%d.txt" path (transaction.Timestamp.ToFileTimeUtc()) 35 | let line = sprintf "%O***%s***%M" transaction.Timestamp transaction.Operation transaction.Amount 36 | File.WriteAllText(filePath, line) -------------------------------------------------------------------------------- /src/code-listings/lesson-24/sample-solution/Scratchpad.fsx: -------------------------------------------------------------------------------- 1 | #load "Domain.fs" 2 | #load "Operations.fs" 3 | 4 | open Capstone4.Operations 5 | open Capstone4.Domain 6 | open System 7 | 8 | type CreditAccount = CreditAccount of Account 9 | 10 | type RatedAccount = 11 | | Credit of CreditAccount 12 | | Overdrawn of Account 13 | 14 | let rateAccount account = 15 | if account.Balance < 0M then Overdrawn account 16 | else Credit(CreditAccount account) 17 | 18 | let withdraw amount (CreditAccount account) = 19 | { account with Balance = account.Balance - amount } 20 | |> rateAccount 21 | 22 | let deposit amount account = 23 | let account = 24 | match account with 25 | | Credit (CreditAccount account) -> account 26 | | Overdrawn account -> account 27 | { account with Balance = account.Balance + amount } 28 | |> rateAccount 29 | 30 | let myAccount = { Balance = 0M; Owner = { Name = "Isaac" }; AccountId = Guid.NewGuid() } |> rateAccount 31 | 32 | myAccount 33 | |> deposit 50M 34 | |> deposit 100M 35 | //|> withdraw 500M <- does not compile :) 36 | 37 | let withdrawSafe amount ratedAccount = 38 | match ratedAccount with 39 | | Credit account -> account |> withdraw amount 40 | | Overdrawn _ -> 41 | printfn "Your account is overdrawn - withdrawal rejected!" 42 | ratedAccount 43 | 44 | myAccount 45 | |> deposit 50M 46 | |> deposit 100M 47 | |> withdrawSafe 500M 48 | |> withdrawSafe 500M 49 | -------------------------------------------------------------------------------- /src/code-listings/lesson-25/CSharpProject/Class1.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 CSharpProject 8 | { 9 | public class Person 10 | { 11 | public string Name { get; private set; } 12 | 13 | public Person(string name) 14 | { 15 | Name = name; 16 | } 17 | 18 | public void PrintName() 19 | { 20 | Console.WriteLine($"My name is {Name}"); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/code-listings/lesson-25/CSharpProject/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("CSharpProject")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("CSharpProject")] 13 | [assembly: AssemblyCopyright("Copyright © 2016")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("a7b88df9-6e34-4e89-a16b-8d222d636f07")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /src/code-listings/lesson-25/FSharpProject/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/code-listings/lesson-25/FSharpProject/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | namespace HybridSolution.AssemblyInfo 2 | 3 | open System.Reflection 4 | open System.Runtime.CompilerServices 5 | open System.Runtime.InteropServices 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [] 11 | [] 12 | [] 13 | [] 14 | [] 15 | [] 16 | [] 17 | [] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [] 23 | 24 | // The following GUID is for the ID of the typelib if this project is exposed to COM 25 | [] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [] 37 | [] 38 | [] 39 | 40 | do 41 | () -------------------------------------------------------------------------------- /src/code-listings/lesson-25/FSharpProject/Program.fs: -------------------------------------------------------------------------------- 1 | [] 2 | let main argv = 3 | let tony = CSharpProject.Person "Tony" 4 | tony.PrintName() 5 | 0 6 | -------------------------------------------------------------------------------- /src/code-listings/lesson-25/Scratchpad.fsx: -------------------------------------------------------------------------------- 1 | // Listing 25.3 2 | #I @"CSharpProject\bin\debug" 3 | #r @"CSharpProject.dll" 4 | // Or all in one line: #r @"CSharpProject\bin\debug\CSharpProject.dll" 5 | 6 | open CSharpProject 7 | let simon = Person "Simon" 8 | simon.PrintName() 9 | 10 | // Listing 25.4 11 | let longhand = 12 | [ "Tony"; "Fred"; "Samantha"; "Brad"; "Sophie "] 13 | |> List.map(fun name -> Person(name)) 14 | 15 | let shorthand = 16 | [ "Tony"; "Fred"; "Samantha"; "Brad"; "Sophie "] 17 | |> List.map Person 18 | 19 | // Listing 25.5 20 | open System.Collections.Generic 21 | 22 | type PersonComparer() = 23 | interface IComparer with 24 | member this.Compare(x, y) = x.Name.CompareTo(y.Name) 25 | 26 | let pComparer = PersonComparer() :> IComparer 27 | pComparer.Compare(simon, Person "Fred") 28 | 29 | // Listing 25.6 30 | let personComparer = 31 | { new IComparer with 32 | member __.Compare(x, y) = x.Name.CompareTo(y.Name) } 33 | 34 | personComparer.Compare(simon, Person "Tony") 35 | 36 | open System 37 | 38 | let blank:string = null 39 | let name = "Vera" 40 | let number = Nullable 10 41 | 42 | let blankAsOption = blank |> Option.ofObj 43 | let nameAsOption = name |> Option.ofObj 44 | let numberAsOption = number |> Option.ofNullable 45 | 46 | let unsafeName = Some "Fred" |> Option.toObj 47 | -------------------------------------------------------------------------------- /src/code-listings/lesson-26/NuGetSample.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.24720.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "NugetFSharp", "NugetFSharp\NugetFSharp.fsproj", "{B01E2A86-7D07-465F-BF66-8F63E2E431F6}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {B01E2A86-7D07-465F-BF66-8F63E2E431F6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {B01E2A86-7D07-465F-BF66-8F63E2E431F6}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {B01E2A86-7D07-465F-BF66-8F63E2E431F6}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {B01E2A86-7D07-465F-BF66-8F63E2E431F6}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /src/code-listings/lesson-26/NugetFSharp/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/code-listings/lesson-26/NugetFSharp/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | namespace NugetFSharp.AssemblyInfo 2 | 3 | open System.Reflection 4 | open System.Runtime.CompilerServices 5 | open System.Runtime.InteropServices 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [] 11 | [] 12 | [] 13 | [] 14 | [] 15 | [] 16 | [] 17 | [] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [] 23 | 24 | // The following GUID is for the ID of the typelib if this project is exposed to COM 25 | [] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [] 37 | [] 38 | [] 39 | 40 | do 41 | () -------------------------------------------------------------------------------- /src/code-listings/lesson-26/NugetFSharp/Library1.fs: -------------------------------------------------------------------------------- 1 | module Library1 2 | 3 | open Newtonsoft.Json 4 | type Person = { Name : string; Age : int } 5 | 6 | let getPerson() = 7 | let text = """{ "Name" : "Sam", "Age" : 18 }""" 8 | let person = JsonConvert.DeserializeObject(text) 9 | printfn "Name is %s with age %d." person.Name person.Age 10 | person 11 | -------------------------------------------------------------------------------- /src/code-listings/lesson-26/NugetFSharp/Script1.fsx: -------------------------------------------------------------------------------- 1 | #I @"..\packages\" 2 | #r @"Humanizer.Core.2.1.0\lib\netstandard1.0\Humanizer.dll" 3 | #r @"Newtonsoft.Json.9.0.1\lib\net45\Newtonsoft.Json.dll" 4 | #load "Library1.fs" 5 | 6 | //#load @"Scripts\load-project-debug.fsx" 7 | 8 | open Humanizer 9 | "ScriptsAreAGreatWayToExplorePackages".Humanize(LetterCasing.AllCaps) 10 | 11 | Library1.getPerson() 12 | -------------------------------------------------------------------------------- /src/code-listings/lesson-26/NugetFSharp/Scripts/load-project-debug.fsx: -------------------------------------------------------------------------------- 1 | // Warning: generated file; your changes could be lost when a new file is generated. 2 | #I __SOURCE_DIRECTORY__ 3 | #load "load-references-debug.fsx" 4 | #load "../AssemblyInfo.fs" 5 | "../Library1.fs" 6 | -------------------------------------------------------------------------------- /src/code-listings/lesson-26/NugetFSharp/Scripts/load-references-debug.fsx: -------------------------------------------------------------------------------- 1 | // Warning: generated file; your changes could be lost when a new file is generated. 2 | #I __SOURCE_DIRECTORY__ 3 | #r "System.Core.dll" 4 | #r "System.dll" 5 | #r "System.Numerics.dll" 6 | #r "../../packages/Microsoft.Azure.KeyVault.Core.1.0.0/lib/net40/Microsoft.Azure.KeyVault.Core.dll" 7 | #r "../../packages/Microsoft.Data.Edm.5.6.4/lib/net40/Microsoft.Data.Edm.dll" 8 | #r "../../packages/Microsoft.Data.OData.5.6.4/lib/net40/Microsoft.Data.OData.dll" 9 | #r "../../packages/Microsoft.Data.Services.Client.5.6.4/lib/net40/Microsoft.Data.Services.Client.dll" 10 | #r "../../packages/WindowsAzure.Storage.7.2.0/lib/net40/Microsoft.WindowsAzure.Storage.dll" 11 | #r "System.Data.dll" 12 | #r "../../packages/System.Spatial.5.6.4/lib/net40/System.Spatial.dll" 13 | #r "../../packages/Humanizer.Core.2.1.0/lib/netstandard1.0/Humanizer.dll" 14 | #r "../../packages/Newtonsoft.Json.9.0.1/lib/net45/Newtonsoft.Json.dll" -------------------------------------------------------------------------------- /src/code-listings/lesson-27/CSharpApp/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/code-listings/lesson-27/CSharpApp/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("CSharpApp")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("")] 12 | [assembly: AssemblyProduct("CSharpApp")] 13 | [assembly: AssemblyCopyright("Copyright © 2016")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("85f4c74d-751d-4880-abce-228b90c0bc73")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /src/code-listings/lesson-27/FSharpCode/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpCode.AssemblyInfo 2 | 3 | open System.Reflection 4 | open System.Runtime.CompilerServices 5 | open System.Runtime.InteropServices 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [] 11 | [] 12 | [] 13 | [] 14 | [] 15 | [] 16 | [] 17 | [] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [] 23 | 24 | // The following GUID is for the ID of the typelib if this project is exposed to COM 25 | [] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [] 37 | [] 38 | [] 39 | 40 | do 41 | () -------------------------------------------------------------------------------- /src/code-listings/lesson-27/FSharpCode/Library1.fs: -------------------------------------------------------------------------------- 1 | namespace Model 2 | 3 | //[] // experiment with this 4 | /// A standard F# record of a Car. 5 | type Car = 6 | { /// The number of wheels on the car. 7 | Wheels : int 8 | /// The brand of the car. 9 | Brand : string 10 | /// The x/y of the car in meters 11 | Dimensions : float * float } 12 | 13 | /// A vehicle of some sort. 14 | type Vehicle = 15 | /// A car is a type of vehicle. 16 | | Motorcar of Car 17 | /// A bike is also a type of vehicle. 18 | | Motorbike of Name:string * EngineSize:float 19 | 20 | module Functions = 21 | /// Describes a vehicle. 22 | let Describe vehicle = 23 | match vehicle with 24 | | Motorcar c -> printfn "This is a car with %d wheels!" c.Wheels 25 | | Motorbike(_, size) -> printfn "This is a bike with engine %f" size 26 | 27 | let CreateCar wheels brand x y = { Wheels = wheels; Brand = brand; Dimensions = x, y } 28 | /// Creates a car with four wheels. 29 | let CreateFourWheeledCar = CreateCar 4 -------------------------------------------------------------------------------- /src/code-listings/lesson-27/FSharpCode/Script.fsx: -------------------------------------------------------------------------------- 1 | // Learn more about F# at http://fsharp.org. See the 'F# Tutorial' project 2 | // for more guidance on F# programming. 3 | 4 | #load "Library1.fs" 5 | open FSharpCode 6 | 7 | // Define your library scripting code here 8 | 9 | -------------------------------------------------------------------------------- /src/code-listings/lesson-27/Interop.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.24720.0 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FSharpCode", "FSharpCode\FSharpCode.fsproj", "{713D020D-7596-4102-9143-626D09345149}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CSharpApp", "CSharpApp\CSharpApp.csproj", "{85F4C74D-751D-4880-ABCE-228B90C0BC73}" 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 | {713D020D-7596-4102-9143-626D09345149}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {713D020D-7596-4102-9143-626D09345149}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {713D020D-7596-4102-9143-626D09345149}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {713D020D-7596-4102-9143-626D09345149}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {85F4C74D-751D-4880-ABCE-228B90C0BC73}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {85F4C74D-751D-4880-ABCE-228B90C0BC73}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {85F4C74D-751D-4880-ABCE-228B90C0BC73}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {85F4C74D-751D-4880-ABCE-228B90C0BC73}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | EndGlobal 29 | -------------------------------------------------------------------------------- /src/code-listings/lesson-29/Capstone5.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25420.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Core", "Core\Core.fsproj", "{5194054C-0572-4E34-9D53-9748B6641595}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WpfClient", "WpfClient\WpfClient.csproj", "{436C01B4-5FCE-42DF-835D-0264E35AC10C}" 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 | {5194054C-0572-4E34-9D53-9748B6641595}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {5194054C-0572-4E34-9D53-9748B6641595}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {5194054C-0572-4E34-9D53-9748B6641595}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {5194054C-0572-4E34-9D53-9748B6641595}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {436C01B4-5FCE-42DF-835D-0264E35AC10C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {436C01B4-5FCE-42DF-835D-0264E35AC10C}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {436C01B4-5FCE-42DF-835D-0264E35AC10C}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {436C01B4-5FCE-42DF-835D-0264E35AC10C}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | EndGlobal 29 | -------------------------------------------------------------------------------- /src/code-listings/lesson-29/Core/Api.fs: -------------------------------------------------------------------------------- 1 | /// Provides access to the banking API. 2 | module Capstone5.Api 3 | 4 | open Capstone5.Domain 5 | open Capstone5.Operations 6 | open System 7 | 8 | /// Loads an account from disk. If no account exists, an empty one is automatically created. 9 | let LoadAccount (customer:Customer) : RatedAccount = 10 | InCredit(CreditAccount { AccountId = Guid.NewGuid() 11 | Balance = 0M 12 | Owner = customer }) 13 | 14 | /// Deposits funds into an account. 15 | let Deposit (amount:decimal) (customer:Customer) : RatedAccount = 16 | InCredit(CreditAccount { AccountId = Guid.NewGuid() 17 | Balance = 0M 18 | Owner = customer }) 19 | 20 | /// Withdraws funds from an account that is in credit. 21 | let Withdraw (amount:decimal) (customer:Customer) : RatedAccount = 22 | InCredit(CreditAccount { AccountId = Guid.NewGuid() 23 | Balance = 0M 24 | Owner = customer }) 25 | 26 | /// Loads the transaction history for an owner. If no transactions exist, returns an empty sequence. 27 | let LoadTransactionHistory(customer:Customer) : Transaction seq = 28 | Seq.empty 29 | 30 | -------------------------------------------------------------------------------- /src/code-listings/lesson-29/Core/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/code-listings/lesson-29/Core/Auditing.fs: -------------------------------------------------------------------------------- 1 | module Capstone5.Auditing 2 | 3 | open Capstone5.Domain 4 | 5 | /// Logs to the console 6 | let printTransaction _ accountId transaction = 7 | printfn "Account %O: %s of %M" accountId transaction.Operation transaction.Amount 8 | 9 | // Logs to both console and file system 10 | let composedLogger = 11 | let loggers = 12 | [ FileRepository.writeTransaction 13 | printTransaction ] 14 | fun accountId owner transaction -> 15 | loggers 16 | |> List.iter(fun logger -> logger accountId owner transaction) -------------------------------------------------------------------------------- /src/code-listings/lesson-29/Core/Domain.fs: -------------------------------------------------------------------------------- 1 | namespace Capstone5.Domain 2 | 3 | open System 4 | 5 | type BankOperation = Deposit | Withdraw 6 | type Customer = { Name : string } 7 | type Account = { AccountId : Guid; Owner : Customer; Balance : decimal } 8 | type Transaction = { Timestamp : DateTime; Operation : string; Amount : decimal } 9 | 10 | /// Represents a bank account that is known to be in credit. 11 | type CreditAccount = CreditAccount of Account 12 | /// A bank account which can either be in credit or overdrawn. 13 | type RatedAccount = 14 | | InCredit of CreditAccount 15 | | Overdrawn of Account 16 | member this.GetField getter = 17 | match this with 18 | | InCredit (CreditAccount account) -> getter account 19 | | Overdrawn account -> getter account 20 | 21 | module Transactions = 22 | /// Serializes a transaction 23 | let serialize transaction = 24 | sprintf "%O***%s***%M" transaction.Timestamp transaction.Operation transaction.Amount 25 | 26 | /// Deserializes a transaction 27 | let deserialize (fileContents:string) = 28 | let parts = fileContents.Split([|"***"|], StringSplitOptions.None) 29 | { Timestamp = DateTime.Parse parts.[0] 30 | Operation = parts.[1] 31 | Amount = Decimal.Parse parts.[2] } -------------------------------------------------------------------------------- /src/code-listings/lesson-29/Core/FileRepository.fs: -------------------------------------------------------------------------------- 1 | module Capstone5.FileRepository 2 | 3 | open Capstone5.Domain 4 | open System.IO 5 | open System 6 | 7 | let private accountsPath = 8 | let path = @"accounts" 9 | Directory.CreateDirectory path |> ignore 10 | path 11 | let private tryFindAccountFolder owner = 12 | let folders = Directory.EnumerateDirectories(accountsPath, sprintf "%s_*" owner) |> Seq.toList 13 | match folders with 14 | | [] -> None 15 | | folder :: _ -> Some(DirectoryInfo(folder).Name) 16 | 17 | let private buildPath(owner, accountId:Guid) = sprintf @"%s\%s_%O" accountsPath owner accountId 18 | 19 | let loadTransactions (folder:string) = 20 | let owner, accountId = 21 | let parts = folder.Split '_' 22 | parts.[0], Guid.Parse parts.[1] 23 | owner, accountId, buildPath(owner, accountId) 24 | |> Directory.EnumerateFiles 25 | |> Seq.map (File.ReadAllText >> Transactions.deserialize) 26 | 27 | /// Finds all transactions from disk for specific owner. 28 | let tryFindTransactionsOnDisk = tryFindAccountFolder >> Option.map loadTransactions 29 | 30 | /// Logs to the file system 31 | let writeTransaction accountId owner transaction = 32 | let path = buildPath(owner, accountId) 33 | path |> Directory.CreateDirectory |> ignore 34 | let filePath = sprintf "%s/%d.txt" path (transaction.Timestamp.ToFileTimeUtc()) 35 | let line = Transactions.serialize transaction 36 | File.WriteAllText(filePath, line) -------------------------------------------------------------------------------- /src/code-listings/lesson-29/Core/Scratchpad.fsx: -------------------------------------------------------------------------------- 1 | #load "Domain.fs" 2 | #load "Operations.fs" 3 | 4 | open Capstone5.Operations 5 | open Capstone5.Domain 6 | open System 7 | -------------------------------------------------------------------------------- /src/code-listings/lesson-29/WpfClient/App.xaml: -------------------------------------------------------------------------------- 1 |  6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/code-listings/lesson-29/WpfClient/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Configuration; 4 | using System.Data; 5 | using System.Linq; 6 | using System.Windows; 7 | 8 | namespace Capstone5 9 | { 10 | /// 11 | /// Interaction logic for App.xaml 12 | /// 13 | public partial class App : Application 14 | { 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/code-listings/lesson-29/WpfClient/Command.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Input; 3 | 4 | namespace Capstone5 5 | { 6 | public class Command : ICommand 7 | { 8 | private readonly Action command; 9 | private readonly Func canExecute; 10 | private readonly Func> tryParse; 11 | 12 | public event EventHandler CanExecuteChanged; 13 | 14 | public Command(Action command, Func> tryParse, Func canExecute = null) 15 | { 16 | this.command = command; 17 | this.tryParse = tryParse; 18 | this.canExecute = canExecute ?? (() => true); 19 | } 20 | 21 | public void Refresh() 22 | { 23 | CanExecuteChanged?.Invoke(this, EventArgs.Empty); 24 | } 25 | 26 | public Boolean CanExecute(Object parameter) 27 | { 28 | return canExecute(); 29 | } 30 | 31 | public void Execute(Object parameter) 32 | { 33 | var result = tryParse(parameter); 34 | if (result.Item1) 35 | { 36 | command(result.Item2); 37 | this.Refresh(); 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/code-listings/lesson-29/WpfClient/FodyWeavers.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/code-listings/lesson-29/WpfClient/MainWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | namespace Capstone5 4 | { 5 | /// 6 | /// Interaction logic for MainWindow.xaml 7 | /// 8 | public partial class MainWindow : Window 9 | { 10 | public MainWindow() 11 | { 12 | InitializeComponent(); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/code-listings/lesson-29/WpfClient/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace Capstone5.Properties { 12 | 13 | 14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.2.0.0")] 16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { 17 | 18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 19 | 20 | public static Settings Default { 21 | get { 22 | return defaultInstance; 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/code-listings/lesson-29/WpfClient/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/code-listings/lesson-29/WpfClient/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/code-listings/lesson-29/WpfClient/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/code-listings/lesson-29/sample-solution/Capstone5.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25420.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Core", "Core\Core.fsproj", "{5194054C-0572-4E34-9D53-9748B6641595}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "WpfClient", "WpfClient\WpfClient.csproj", "{436C01B4-5FCE-42DF-835D-0264E35AC10C}" 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 | {5194054C-0572-4E34-9D53-9748B6641595}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {5194054C-0572-4E34-9D53-9748B6641595}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {5194054C-0572-4E34-9D53-9748B6641595}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {5194054C-0572-4E34-9D53-9748B6641595}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {436C01B4-5FCE-42DF-835D-0264E35AC10C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {436C01B4-5FCE-42DF-835D-0264E35AC10C}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {436C01B4-5FCE-42DF-835D-0264E35AC10C}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {436C01B4-5FCE-42DF-835D-0264E35AC10C}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | EndGlobal 29 | -------------------------------------------------------------------------------- /src/code-listings/lesson-29/sample-solution/Core/Api.fs: -------------------------------------------------------------------------------- 1 | /// Provides access to the banking API. 2 | module Capstone5.Api 3 | 4 | open Capstone5.Domain 5 | open Capstone5.Operations 6 | open System 7 | 8 | /// Loads an account from disk. If no account exists, an empty one is automatically created. 9 | let LoadAccount customer = 10 | customer.Name 11 | |> FileRepository.tryFindTransactionsOnDisk 12 | |> Option.map Operations.loadAccount 13 | |> defaultArg <| 14 | InCredit(CreditAccount { AccountId = Guid.NewGuid() 15 | Balance = 0M 16 | Owner = customer }) 17 | /// Deposits funds into an account. 18 | let Deposit amount customer = 19 | let ratedAccount = LoadAccount customer 20 | let accountId = ratedAccount.GetField (fun a -> a.AccountId) 21 | let owner = ratedAccount.GetField(fun a -> a.Owner) 22 | auditAs "deposit" Auditing.composedLogger deposit amount ratedAccount accountId owner 23 | 24 | /// Withdraws funds from an account that is in credit. 25 | let Withdraw amount customer = 26 | let account = LoadAccount customer 27 | match account with 28 | | InCredit (CreditAccount account as creditAccount) -> auditAs "withdraw" Auditing.composedLogger withdraw amount creditAccount account.AccountId account.Owner 29 | | account -> account 30 | 31 | /// Loads the transaction history for an owner. 32 | let LoadTransactionHistory customer = 33 | customer.Name 34 | |> FileRepository.tryFindTransactionsOnDisk 35 | |> Option.map(fun (_,_,txns) -> txns) 36 | |> defaultArg <| Seq.empty -------------------------------------------------------------------------------- /src/code-listings/lesson-29/sample-solution/Core/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/code-listings/lesson-29/sample-solution/Core/Auditing.fs: -------------------------------------------------------------------------------- 1 | module internal Capstone5.Auditing 2 | 3 | open Capstone5.Domain 4 | 5 | /// Logs to the console 6 | let printTransaction _ accountId transaction = 7 | printfn "Account %O: %s of %M" accountId transaction.Operation transaction.Amount 8 | 9 | // Logs to both console and file system 10 | let composedLogger = 11 | let loggers = 12 | [ FileRepository.writeTransaction 13 | printTransaction ] 14 | fun accountId owner transaction -> 15 | loggers 16 | |> List.iter(fun logger -> logger accountId owner transaction) -------------------------------------------------------------------------------- /src/code-listings/lesson-29/sample-solution/Core/Domain.fs: -------------------------------------------------------------------------------- 1 | namespace Capstone5.Domain 2 | 3 | open System 4 | 5 | type internal BankOperation = Deposit | Withdraw 6 | 7 | /// A customer of the bank. 8 | type Customer = { Name : string } 9 | /// An account held at the bank. 10 | type Account = { AccountId : Guid; Owner : Customer; Balance : decimal } 11 | /// A single transaction that has occurred. 12 | type Transaction = { Timestamp : DateTime; Operation : string; Amount : decimal } 13 | 14 | /// Represents a bank account that is known to be in credit. 15 | type CreditAccount = CreditAccount of Account 16 | /// A bank account which can either be in credit or overdrawn. 17 | type RatedAccount = 18 | /// Represents an account that is known to be in credit. 19 | | InCredit of Account:CreditAccount 20 | /// Represents an account that is known to be overdrawn. 21 | | Overdrawn of Account:Account 22 | member internal this.GetField getter = 23 | match this with 24 | | InCredit (CreditAccount account) -> getter account 25 | | Overdrawn account -> getter account 26 | /// Gets the current balance of the account. 27 | member this.Balance = this.GetField(fun a -> a.Balance) -------------------------------------------------------------------------------- /src/code-listings/lesson-29/sample-solution/Core/FileRepository.fs: -------------------------------------------------------------------------------- 1 | module internal Capstone5.FileRepository 2 | 3 | open Capstone5.Domain 4 | open System.IO 5 | open System 6 | open Newtonsoft.Json 7 | 8 | let private accountsPath = 9 | let path = @"accounts" 10 | Directory.CreateDirectory path |> ignore 11 | path 12 | let private tryFindAccountFolder owner = 13 | let folders = Directory.EnumerateDirectories(accountsPath, sprintf "%s_*" owner) |> Seq.toList 14 | match folders with 15 | | [] -> None 16 | | folder :: _ -> Some(DirectoryInfo(folder).Name) 17 | 18 | let private buildPath(owner, accountId:Guid) = sprintf @"%s\%s_%O" accountsPath owner accountId 19 | 20 | let loadTransactions (folder:string) = 21 | let owner, accountId = 22 | let parts = folder.Split '_' 23 | parts.[0], Guid.Parse parts.[1] 24 | owner, accountId, buildPath(owner, accountId) 25 | |> Directory.EnumerateFiles 26 | |> Seq.map (fun path -> JsonConvert.DeserializeObject(File.ReadAllText path)) 27 | 28 | /// Finds all transactions from disk for specific owner. 29 | let tryFindTransactionsOnDisk = tryFindAccountFolder >> Option.map loadTransactions 30 | 31 | /// Logs to the file system 32 | let writeTransaction accountId owner transaction = 33 | let path = buildPath(owner, accountId) 34 | path |> Directory.CreateDirectory |> ignore 35 | let filePath = sprintf "%s/%d.txt" path (transaction.Timestamp.ToFileTimeUtc()) 36 | let line = transaction |> JsonConvert.SerializeObject 37 | File.WriteAllText(filePath, line) -------------------------------------------------------------------------------- /src/code-listings/lesson-29/sample-solution/Core/FodyWeavers.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | -------------------------------------------------------------------------------- /src/code-listings/lesson-29/sample-solution/Core/Scratchpad.fsx: -------------------------------------------------------------------------------- 1 | #load "Domain.fs" 2 | #load "Operations.fs" 3 | 4 | open Capstone5.Operations 5 | open Capstone5.Domain 6 | open System 7 | -------------------------------------------------------------------------------- /src/code-listings/lesson-29/sample-solution/Core/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | -------------------------------------------------------------------------------- /src/code-listings/lesson-29/sample-solution/WpfClient/App.xaml: -------------------------------------------------------------------------------- 1 |  6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/code-listings/lesson-29/sample-solution/WpfClient/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Configuration; 4 | using System.Data; 5 | using System.Linq; 6 | using System.Windows; 7 | 8 | namespace Capstone5 9 | { 10 | /// 11 | /// Interaction logic for App.xaml 12 | /// 13 | public partial class App : Application 14 | { 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/code-listings/lesson-29/sample-solution/WpfClient/Command.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Input; 3 | 4 | namespace Capstone5 5 | { 6 | public class Command : ICommand 7 | { 8 | private readonly Action command; 9 | private readonly Func canExecute; 10 | private readonly Func> tryParse; 11 | 12 | public event EventHandler CanExecuteChanged; 13 | 14 | public Command(Action command, Func> tryParse, Func canExecute = null) 15 | { 16 | this.command = command; 17 | this.tryParse = tryParse; 18 | this.canExecute = canExecute ?? (() => true); 19 | } 20 | 21 | public void Refresh() 22 | { 23 | CanExecuteChanged?.Invoke(this, EventArgs.Empty); 24 | } 25 | 26 | public Boolean CanExecute(Object parameter) 27 | { 28 | return canExecute(); 29 | } 30 | 31 | public void Execute(Object parameter) 32 | { 33 | var result = tryParse(parameter); 34 | if (result.Item1) 35 | { 36 | command(result.Item2); 37 | this.Refresh(); 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/code-listings/lesson-29/sample-solution/WpfClient/FodyWeavers.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/code-listings/lesson-29/sample-solution/WpfClient/MainWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | namespace Capstone5 4 | { 5 | /// 6 | /// Interaction logic for MainWindow.xaml 7 | /// 8 | public partial class MainWindow : Window 9 | { 10 | public MainWindow() 11 | { 12 | InitializeComponent(); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/code-listings/lesson-29/sample-solution/WpfClient/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace Capstone5.Properties { 12 | 13 | 14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "16.2.0.0")] 16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { 17 | 18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 19 | 20 | public static Settings Default { 21 | get { 22 | return defaultInstance; 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/code-listings/lesson-29/sample-solution/WpfClient/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/code-listings/lesson-29/sample-solution/WpfClient/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/code-listings/lesson-29/sample-solution/WpfClient/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/code-listings/lesson-30-1.fsx: -------------------------------------------------------------------------------- 1 | // Listing 30.1 2 | #I @"..\..\packages" 3 | #r @"FSharp.Data\lib\net40\FSharp.Data.dll" 4 | #r @"Newtonsoft.Json\lib\net45\Newtonsoft.Json.dll" 5 | 6 | open FSharp.Data 7 | type Football = CsvProvider< @"..\..\data\FootballResults.csv"> 8 | let data = Football.GetSample().Rows |> Seq.toArray 9 | 10 | // Listing 30.2 11 | #r @"Google.DataTable.Net.Wrapper\lib\Google.DataTable.Net.Wrapper.dll" 12 | #r @"XPlot.GoogleCharts\lib\net45\XPlot.GoogleCharts.dll" 13 | open XPlot.GoogleCharts 14 | 15 | data 16 | |> Seq.filter(fun row -> 17 | row.``Full Time Home Goals`` > row.``Full Time Away Goals``) 18 | |> Seq.countBy(fun row -> row.``Home Team``) 19 | |> Seq.sortByDescending snd 20 | |> Seq.take 10 21 | |> Chart.Column 22 | |> Chart.Show -------------------------------------------------------------------------------- /src/code-listings/lesson-30-2.fsx: -------------------------------------------------------------------------------- 1 | // This script is provided as an example of how you might manually parse a CSV file in F#. 2 | 3 | open System 4 | open System.IO 5 | Environment.CurrentDirectory <- __SOURCE_DIRECTORY__ 6 | 7 | let file = @"..\..\data\FootballResults.csv" 8 | type Result = 9 | { Date : DateTime 10 | Home : string 11 | Away : string 12 | HomeGoals : int 13 | AwayGoals : int } 14 | 15 | let data = 16 | file 17 | |> File.ReadAllLines 18 | |> Seq.skip 1 19 | |> Seq.map(fun row -> 20 | let fields = row.Split ',' 21 | { Date = DateTime.ParseExact(fields.[0], "MM/dd/yyyy", null) 22 | Home = fields.[1] 23 | Away = fields.[2] 24 | HomeGoals = int fields.[3] 25 | AwayGoals = int fields.[4] }) 26 | 27 | data 28 | |> Seq.filter(fun row -> row.HomeGoals > row.AwayGoals) 29 | |> Seq.countBy(fun row -> row.Home) 30 | |> Seq.sortByDescending snd 31 | |> Seq.take 3 32 | -------------------------------------------------------------------------------- /src/code-listings/lesson-31.fsx: -------------------------------------------------------------------------------- 1 | // Listing 31.1 2 | #I @"..\..\packages" 3 | #r @"FSharp.Data\lib\net40\FSharp.Data.dll" 4 | 5 | open FSharp.Data 6 | 7 | type TvListing = JsonProvider<"http://www.bbc.co.uk/programmes/genres/comedy/schedules/upcoming.json"> 8 | let tvListing = TvListing.GetSample() 9 | let title = tvListing.Broadcasts.[0].Programme.DisplayTitles.Title 10 | 11 | // Now you try 12 | #r @"Google.DataTable.Net.Wrapper\lib\Google.DataTable.Net.Wrapper.dll" 13 | #r @"XPlot.GoogleCharts\lib\net45\XPlot.GoogleCharts.dll" 14 | open XPlot.GoogleCharts 15 | 16 | type Films = HtmlProvider<"https://en.wikipedia.org/wiki/Robert_De_Niro_filmography"> 17 | let deNiro = Films.GetSample() 18 | 19 | deNiro.Tables.Film.Rows 20 | |> Array.countBy(fun row -> string row.Year) 21 | |> Chart.SteppedArea 22 | |> Chart.Show 23 | 24 | 25 | 26 | // Now you try #2 27 | type Package = HtmlProvider< @"..\..\data\sample-package.html"> 28 | 29 | let ef = Package.Load("https://www.nuget.org/packages/entityframework") 30 | let nunit = Package.Load("https://www.nuget.org/packages/nunit") 31 | let newtonsoft = Package.Load("https://www.nuget.org/packages/newtonsoft.json") 32 | 33 | // Listing 31.2 34 | [ ef; nunit; newtonsoft ] 35 | |> Seq.collect(fun package -> package.Tables.``Version History``.Rows) 36 | |> Seq.sortByDescending(fun versionHistory -> versionHistory.Downloads) 37 | |> Seq.take 10 38 | |> Seq.map(fun vh -> vh.Version, vh.Downloads) 39 | |> Chart.Column 40 | |> Chart.Show 41 | -------------------------------------------------------------------------------- /src/code-listings/lesson-32-1.fsx: -------------------------------------------------------------------------------- 1 | #r @"..\..\packages\FSharp.Data.SqlClient\lib\net40\FSharp.Data.SqlClient.dll" 2 | 3 | open FSharp.Data 4 | 5 | // Listing 32.1 6 | let [] Conn = @"Server=(localdb)\MSSQLLocalDb;Database=AdventureWorksLT;Integrated Security=SSPI" 7 | type GetCustomers = SqlCommandProvider<"SELECT * FROM SalesLT.Customer", Conn> 8 | let customers = GetCustomers.Create(Conn).Execute() |> Seq.toArray 9 | let customer = customers.[0] 10 | 11 | // Now you try #1 12 | printfn "%s %s works for %s" customer.FirstName customer.LastName (customer.CompanyName |> defaultArg <| "no-one") 13 | 14 | // Now you try #2 15 | type AdventureWorks = SqlProgrammabilityProvider 16 | let productCategory = new AdventureWorks.SalesLT.Tables.ProductCategory() 17 | productCategory.AddRow("Mittens", Some 3) 18 | productCategory.AddRow("Long Shorts", Some 3) 19 | productCategory.AddRow("Wooly Hats", Some 4) 20 | productCategory.Update() 21 | 22 | // Listing 32.2 23 | type Categories = SqlEnumProvider<"SELECT Name, ProductCategoryId FROM SalesLT.ProductCategory", Conn> 24 | let woolyHats = Categories.``Wooly Hats`` 25 | printfn "Wooly Hats has ID %d" woolyHats -------------------------------------------------------------------------------- /src/code-listings/lesson-32-2.fsx: -------------------------------------------------------------------------------- 1 | // Listing 32.3 2 | #r @"..\..\packages\SQLProvider\lib\FSharp.Data.SqlProvider.dll" 3 | open FSharp.Data.Sql 4 | 5 | type AdventureWorks = SqlDataProvider 6 | 7 | let context = AdventureWorks.GetDataContext() 8 | 9 | type Customer = { Name : string } 10 | 11 | let customers = 12 | query { 13 | for customer in context.SalesLt.Customer do 14 | take 10 15 | } |> Seq.toArray 16 | let customer = customers.[0] 17 | 18 | // Listing 32.4 19 | let names = 20 | query { 21 | for customer in context.SalesLt.Customer do 22 | where (customer.CompanyName = Some "Sharp Bikes") 23 | select { Name = (customer.FirstName + " " + customer.LastName) } 24 | distinct 25 | } |> Seq.toArray 26 | 27 | // Listing 32.5 28 | let category = context.SalesLt.ProductCategory.Create() 29 | category.ParentProductCategoryId <- Some 3 30 | category.Name <- "Scarf" 31 | context.SubmitUpdates() 32 | 33 | // Listing 32.6 34 | let mittens = 35 | context.SalesLt.ProductCategory 36 | .Individuals 37 | .``As Name`` 38 | . 39 | -------------------------------------------------------------------------------- /src/code-listings/lesson-34/SqlDemo/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/code-listings/lesson-34/SqlDemo/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | namespace SqlDemo.AssemblyInfo 2 | 3 | open System.Reflection 4 | open System.Runtime.CompilerServices 5 | open System.Runtime.InteropServices 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [] 11 | [] 12 | [] 13 | [] 14 | [] 15 | [] 16 | [] 17 | [] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [] 23 | 24 | // The following GUID is for the ID of the typelib if this project is exposed to COM 25 | [] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [] 37 | [] 38 | [] 39 | 40 | do 41 | () -------------------------------------------------------------------------------- /src/code-listings/lesson-34/SqlDemo/CustomerRepository.fs: -------------------------------------------------------------------------------- 1 | module CustomerRepository 2 | 3 | open FSharp.Data 4 | 5 | let [] private CompileTimeConnection = "Server=(localdb)\MSSQLLocalDb;Database=AdventureWorksLT;Integrated Security=SSPI" 6 | type private GetCustomers = SqlCommandProvider<"SELECT TOP 50 * FROM SalesLT.Customer", CompileTimeConnection> 7 | 8 | let printCustomers(runtimeConnection:string) = 9 | let customers = GetCustomers.Create(runtimeConnection) 10 | 11 | customers.Execute() 12 | |> Seq.iter (fun c -> printfn "%A: %s %s" c.CompanyName c.FirstName c.LastName) 13 | 14 | -------------------------------------------------------------------------------- /src/code-listings/lesson-34/SqlDemo/DataAccessThroughScript.fsx: -------------------------------------------------------------------------------- 1 | #load "scripts/load-references-debug.fsx" 2 | #load "CustomerRepository.fs" 3 | 4 | let scriptConnectionString = "Server=(localdb)\MSSQLLocalDb;Database=AdventureWorksLT;Integrated Security=SSPI" 5 | 6 | CustomerRepository.printCustomers(scriptConnectionString) 7 | -------------------------------------------------------------------------------- /src/code-listings/lesson-34/SqlDemo/Program.fs: -------------------------------------------------------------------------------- 1 | open FSharp.Data 2 | 3 | let [] Conn = "Server=(localdb)\MSSQLLocalDb;Database=AdventureWorksLT;Integrated Security=SSPI" 4 | type GetCustomers = SqlCommandProvider<"SELECT TOP 50 * FROM SalesLT.Customer", Conn> 5 | 6 | [] 7 | let main _ = 8 | let customers = GetCustomers.Create(Conn) 9 | 10 | customers.Execute() 11 | |> Seq.iter (fun c -> printfn "%A: %s %s" c.CompanyName c.FirstName c.LastName) 12 | 13 | 0 14 | -------------------------------------------------------------------------------- /src/code-listings/lesson-34/SqlDemo/Scripts/load-project-debug.fsx: -------------------------------------------------------------------------------- 1 | // Warning: generated file; your changes could be lost when a new file is generated. 2 | #I __SOURCE_DIRECTORY__ 3 | #load "load-references-debug.fsx" 4 | #load "../AssemblyInfo.fs" 5 | "../CustomerRepository.fs" 6 | "../Program.fs" 7 | -------------------------------------------------------------------------------- /src/code-listings/lesson-34/SqlDemo/Scripts/load-references-debug.fsx: -------------------------------------------------------------------------------- 1 | // Warning: generated file; your changes could be lost when a new file is generated. 2 | #I __SOURCE_DIRECTORY__ 3 | #r "../../../../../packages/FSharp.Data.SqlClient/lib/net40/FSharp.Data.SqlClient.dll" 4 | #r "../../../../../packages/FSharp.Data.SqlClient/lib/net40/Microsoft.SqlServer.TransactSql.ScriptDom.dll" 5 | #r "System.Configuration.dll" 6 | #r "System.Core.dll" 7 | #r "System.Data.dll" 8 | #r "System.dll" 9 | #r "System.Numerics.dll" -------------------------------------------------------------------------------- /src/code-listings/lesson-34/TypeProviderConfig.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25420.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "SqlDemo", "SqlDemo\SqlDemo.fsproj", "{D4040FC0-A4CF-419C-A535-2FB8B9357B6E}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {D4040FC0-A4CF-419C-A535-2FB8B9357B6E}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {D4040FC0-A4CF-419C-A535-2FB8B9357B6E}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {D4040FC0-A4CF-419C-A535-2FB8B9357B6E}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {D4040FC0-A4CF-419C-A535-2FB8B9357B6E}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /src/code-listings/lesson-35/BankAccountDb/Account.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE [dbo].[Account] 2 | ( 3 | [Owner] VARCHAR(256) NOT NULL PRIMARY KEY, 4 | [AccountId] UNIQUEIDENTIFIER NOT NULL 5 | ) 6 | 7 | GO 8 | 9 | CREATE UNIQUE INDEX [IX_Account_AccountId] ON [dbo].[Account] ([AccountId]) 10 | -------------------------------------------------------------------------------- /src/code-listings/lesson-35/BankAccountDb/AccountTransaction.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE [dbo].[AccountTransaction] 2 | ( 3 | [AccountTransactionId] INT NOT NULL PRIMARY KEY NONCLUSTERED IDENTITY(1,1), 4 | [AccountId] UNIQUEIDENTIFIER NOT NULL, 5 | [Timestamp] DATETIME NOT NULL, 6 | [OperationId] int NOT NULL, 7 | [Amount] decimal NOT NULL, 8 | CONSTRAINT [FK_AccountTransaction_Account] FOREIGN KEY ([AccountId]) REFERENCES [dbo].[Account]([AccountId]), 9 | CONSTRAINT [FK_AccountTransaction_Operation] FOREIGN KEY ([OperationId]) REFERENCES [dbo].[Operation]([OperationId]) 10 | ) 11 | 12 | GO 13 | 14 | CREATE CLUSTERED INDEX [IX_Transaction_AccountId] ON [dbo].[AccountTransaction] ([AccountId]) 15 | -------------------------------------------------------------------------------- /src/code-listings/lesson-35/BankAccountDb/BankAccountDb.publish.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | True 5 | BankAccountDb 6 | BankAccountDb.sql 7 | Data Source=(localdb)\MSSQLLocalDB;Integrated Security=True;Persist Security Info=False;Pooling=False;MultipleActiveResultSets=False;Connect Timeout=60;Encrypt=False;TrustServerCertificate=True 8 | 1 9 | True 10 | True 11 | 12 | -------------------------------------------------------------------------------- /src/code-listings/lesson-35/BankAccountDb/BankAccountDb.refactorlog: -------------------------------------------------------------------------------- 1 |  2 | 3 | -------------------------------------------------------------------------------- /src/code-listings/lesson-35/BankAccountDb/Operation.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE [dbo].[Operation] 2 | ( 3 | [OperationId] INT NOT NULL PRIMARY KEY, 4 | [Description] VARCHAR(32) NOT NULL 5 | ) 6 | -------------------------------------------------------------------------------- /src/code-listings/lesson-35/Core/Api.fs: -------------------------------------------------------------------------------- 1 | /// Provides access to the banking API. 2 | module Capstone6.Api 3 | 4 | open Capstone6.Domain 5 | open Capstone6.Operations 6 | open System 7 | 8 | /// Loads an account from disk. If no account exists, an empty one is automatically created. 9 | let LoadAccount customer = 10 | customer.Name 11 | |> FileRepository.tryFindTransactionsOnDisk 12 | |> Option.map (Operations.buildAccount customer.Name) 13 | |> defaultArg <| 14 | InCredit(CreditAccount { AccountId = Guid.NewGuid() 15 | Balance = 0M 16 | Owner = customer }) 17 | /// Deposits funds into an account. 18 | let Deposit amount customer = 19 | let ratedAccount = LoadAccount customer 20 | let accountId = ratedAccount.GetField (fun a -> a.AccountId) 21 | let owner = ratedAccount.GetField(fun a -> a.Owner) 22 | auditAs Deposit FileRepository.writeTransaction deposit amount ratedAccount accountId owner 23 | 24 | /// Withdraws funds from an account that is in credit. 25 | let Withdraw amount customer = 26 | let account = LoadAccount customer 27 | match account with 28 | | InCredit (CreditAccount account as creditAccount) -> auditAs Withdraw FileRepository.writeTransaction withdraw amount creditAccount account.AccountId account.Owner 29 | | account -> account 30 | 31 | /// Loads the transaction history for an owner. 32 | let LoadTransactionHistory customer = 33 | customer.Name 34 | |> FileRepository.tryFindTransactionsOnDisk 35 | |> Option.map(fun (_,txns) -> txns) 36 | |> defaultArg <| Seq.empty -------------------------------------------------------------------------------- /src/code-listings/lesson-35/Core/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /src/code-listings/lesson-35/Core/Domain.fs: -------------------------------------------------------------------------------- 1 | namespace Capstone6.Domain 2 | 3 | open System 4 | 5 | type BankOperation = Deposit | Withdraw 6 | 7 | /// A customer of the bank. 8 | type Customer = { Name : string } 9 | /// An account held at the bank. 10 | type Account = { AccountId : Guid; Owner : Customer; Balance : decimal } 11 | /// A single transaction that has occurred. 12 | type Transaction = { Timestamp : DateTime; Operation : BankOperation; Amount : decimal } 13 | 14 | /// Represents a bank account that is known to be in credit. 15 | type CreditAccount = CreditAccount of Account 16 | /// A bank account which can either be in credit or overdrawn. 17 | type RatedAccount = 18 | /// Represents an account that is known to be in credit. 19 | | InCredit of Account:CreditAccount 20 | /// Represents an account that is known to be overdrawn. 21 | | Overdrawn of Account:Account 22 | member internal this.GetField getter = 23 | match this with 24 | | InCredit (CreditAccount account) -> getter account 25 | | Overdrawn account -> getter account 26 | /// Gets the current balance of the account. 27 | member this.Balance = this.GetField(fun a -> a.Balance) -------------------------------------------------------------------------------- /src/code-listings/lesson-35/Core/FileRepository.fs: -------------------------------------------------------------------------------- 1 | module internal Capstone6.FileRepository 2 | 3 | open Capstone6.Domain 4 | open System.IO 5 | open System 6 | open Newtonsoft.Json 7 | 8 | let private accountsPath = 9 | let path = @"accounts" 10 | Directory.CreateDirectory path |> ignore 11 | path 12 | let private tryFindAccountFolder owner = 13 | let folders = Directory.EnumerateDirectories(accountsPath, sprintf "%s_*" owner) |> Seq.toList 14 | match folders with 15 | | [] -> None 16 | | folder :: _ -> Some(DirectoryInfo(folder).Name) 17 | 18 | let private buildPath(owner, accountId:Guid) = sprintf @"%s\%s_%O" accountsPath owner accountId 19 | 20 | let private loadTransactions (folder:string) = 21 | let owner, accountId = 22 | let parts = folder.Split '_' 23 | parts.[0], Guid.Parse parts.[1] 24 | accountId, buildPath(owner, accountId) 25 | |> Directory.EnumerateFiles 26 | |> Seq.map (fun path -> JsonConvert.DeserializeObject(File.ReadAllText path)) 27 | 28 | /// Finds all transactions from disk for specific owner. 29 | let tryFindTransactionsOnDisk = tryFindAccountFolder >> Option.map loadTransactions 30 | 31 | /// Logs to the file system 32 | let writeTransaction accountId owner transaction = 33 | let path = buildPath(owner, accountId) 34 | path |> Directory.CreateDirectory |> ignore 35 | let filePath = sprintf "%s/%d.json" path (transaction.Timestamp.ToFileTimeUtc()) 36 | let line = transaction |> JsonConvert.SerializeObject 37 | File.WriteAllText(filePath, line) -------------------------------------------------------------------------------- /src/code-listings/lesson-35/Core/Scratchpad.fsx: -------------------------------------------------------------------------------- 1 | #load "Domain.fs" 2 | #load "Operations.fs" 3 | #r @"..\packages\FSharp.Data.SqlClient.1.8.2\lib\net40\FSharp.Data.SqlClient.dll" 4 | #load "SqlRepository.fs" 5 | 6 | open Capstone6.Operations 7 | open Capstone6.Domain 8 | open System 9 | open FSharp.Data 10 | 11 | // Copied from SqlRepository.fs, this allows you to test out the queries and commands in isolation. 12 | let [] Conn = @"Data Source=(localdb)\MSSQLLocalDB;Database=BankAccountDb;Integrated Security=True;Connect Timeout=60" 13 | type AccountsDb = SqlProgrammabilityProvider 14 | type GetAccountId = SqlCommandProvider<"SELECT TOP 1 AccountId FROM dbo.Account WHERE Owner = @owner", Conn, SingleRow = true> 15 | type FindTransactions = SqlCommandProvider<"SELECT Timestamp, OperationId, Amount FROM dbo.AccountTransaction WHERE AccountId = @accountId", Conn> 16 | type FindTransactionsByOwner = SqlCommandProvider<"SELECT a.AccountId, at.Timestamp, at.OperationId, at.Amount FROM dbo.Account a LEFT JOIN dbo.AccountTransaction at on a.AccountId = at.AccountId WHERE Owner = @owner", Conn> 17 | type DbOperations = SqlEnumProvider<"SELECT Description, OperationId FROM dbo.Operation", Conn> 18 | 19 | // Get an accountId and then associated transactions. Note that I'm using .Value to "unwrap" the 20 | // optional accountId. This is unsafe and should NEVER be done in "real" code; use either pattern 21 | // matching or Option.map. I'm using it here as (a) this is a demo script, and (b) the database 22 | // is primed with an account that I know about. 23 | let accountId = GetAccountId.Create(Conn).Execute("isaac") 24 | let transactions = FindTransactions.Create(Conn).Execute(accountId.Value) |> Seq.toArray -------------------------------------------------------------------------------- /src/code-listings/lesson-35/Core/SqlRepository.fs: -------------------------------------------------------------------------------- 1 | module internal Capstone6.SqlRepository 2 | 3 | open Capstone6.Domain 4 | open FSharp.Data 5 | open System 6 | 7 | [] 8 | module private DB = 9 | let [] Conn = "Name=AccountsDb" 10 | type AccountsDb = SqlProgrammabilityProvider 11 | type GetAccountId = SqlCommandProvider<"SELECT TOP 1 AccountId FROM dbo.Account WHERE Owner = @owner", Conn, SingleRow = true> 12 | type FindTransactions = SqlCommandProvider<"SELECT Timestamp, OperationId, Amount FROM dbo.AccountTransaction WHERE AccountId = @accountId", Conn> 13 | type FindTransactionsByOwner = SqlCommandProvider<"SELECT a.AccountId, at.Timestamp, at.OperationId, at.Amount FROM dbo.Account a LEFT JOIN dbo.AccountTransaction at on a.AccountId = at.AccountId WHERE Owner = @owner", Conn> 14 | type DbOperations = SqlEnumProvider<"SELECT Description, OperationId FROM dbo.Operation", Conn> 15 | 16 | 17 | let getAccountAndTransactions (owner:string) : (Guid * Transaction seq) option = 18 | None 19 | 20 | let writeTransaction (accountId:Guid) (owner:string) (transaction:Transaction) = 21 | () -------------------------------------------------------------------------------- /src/code-listings/lesson-35/Core/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/code-listings/lesson-35/WpfClient/App.xaml: -------------------------------------------------------------------------------- 1 |  6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/code-listings/lesson-35/WpfClient/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Configuration; 4 | using System.Data; 5 | using System.Linq; 6 | using System.Windows; 7 | 8 | namespace Capstone6 9 | { 10 | /// 11 | /// Interaction logic for App.xaml 12 | /// 13 | public partial class App : Application 14 | { 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/code-listings/lesson-35/WpfClient/BankOperationConverter.cs: -------------------------------------------------------------------------------- 1 | using Capstone6.Domain; 2 | using System; 3 | using System.Globalization; 4 | using System.Windows.Data; 5 | 6 | namespace Capstone6.Converters 7 | { 8 | public class BankOperationConverter : IValueConverter 9 | { 10 | public Object Convert(Object value, Type targetType, Object parameter, CultureInfo culture) 11 | { 12 | var bankOperation = value as BankOperation; 13 | if (bankOperation == null) 14 | return "Unknown"; 15 | else if (bankOperation.IsWithdraw) 16 | return "Withdraw"; 17 | else 18 | return "Deposit"; 19 | } 20 | 21 | public Object ConvertBack(Object value, Type targetType, Object parameter, CultureInfo culture) 22 | { 23 | throw new NotImplementedException(); 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/code-listings/lesson-35/WpfClient/Command.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Input; 3 | 4 | namespace Capstone6 5 | { 6 | public class Command : ICommand 7 | { 8 | private readonly Action command; 9 | private readonly Func canExecute; 10 | private readonly Func> tryParse; 11 | 12 | public event EventHandler CanExecuteChanged; 13 | 14 | public Command(Action command, Func> tryParse, Func canExecute = null) 15 | { 16 | this.command = command; 17 | this.tryParse = tryParse; 18 | this.canExecute = canExecute ?? (() => true); 19 | } 20 | 21 | public void Refresh() 22 | { 23 | CanExecuteChanged?.Invoke(this, EventArgs.Empty); 24 | } 25 | 26 | public Boolean CanExecute(Object parameter) 27 | { 28 | return canExecute(); 29 | } 30 | 31 | public void Execute(Object parameter) 32 | { 33 | var result = tryParse(parameter); 34 | if (result.Item1) 35 | { 36 | command(result.Item2); 37 | this.Refresh(); 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/code-listings/lesson-35/WpfClient/FodyWeavers.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/code-listings/lesson-35/WpfClient/MainWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | namespace Capstone6 4 | { 5 | /// 6 | /// Interaction logic for MainWindow.xaml 7 | /// 8 | public partial class MainWindow : Window 9 | { 10 | public MainWindow() 11 | { 12 | InitializeComponent(); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/code-listings/lesson-35/WpfClient/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace Capstone6.Properties { 12 | 13 | 14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "14.0.0.0")] 16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { 17 | 18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 19 | 20 | public static Settings Default { 21 | get { 22 | return defaultInstance; 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/code-listings/lesson-35/WpfClient/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/code-listings/lesson-35/WpfClient/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/code-listings/lesson-35/WpfClient/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/code-listings/lesson-35/sample-solution/BankAccountDb/Account.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE [dbo].[Account] 2 | ( 3 | [Owner] VARCHAR(256) NOT NULL PRIMARY KEY, 4 | [AccountId] UNIQUEIDENTIFIER NOT NULL 5 | ) 6 | 7 | GO 8 | 9 | CREATE UNIQUE INDEX [IX_Account_AccountId] ON [dbo].[Account] ([AccountId]) 10 | -------------------------------------------------------------------------------- /src/code-listings/lesson-35/sample-solution/BankAccountDb/AccountTransaction.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE [dbo].[AccountTransaction] 2 | ( 3 | [AccountTransactionId] INT NOT NULL PRIMARY KEY NONCLUSTERED IDENTITY(1,1), 4 | [AccountId] UNIQUEIDENTIFIER NOT NULL, 5 | [Timestamp] DATETIME NOT NULL, 6 | [OperationId] int NOT NULL, 7 | [Amount] decimal NOT NULL, 8 | CONSTRAINT [FK_AccountTransaction_Account] FOREIGN KEY ([AccountId]) REFERENCES [dbo].[Account]([AccountId]), 9 | CONSTRAINT [FK_AccountTransaction_Operation] FOREIGN KEY ([OperationId]) REFERENCES [dbo].[Operation]([OperationId]) 10 | ) 11 | 12 | GO 13 | 14 | CREATE CLUSTERED INDEX [IX_Transaction_AccountId] ON [dbo].[AccountTransaction] ([AccountId]) 15 | -------------------------------------------------------------------------------- /src/code-listings/lesson-35/sample-solution/BankAccountDb/BankAccountDb.publish.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | True 5 | BankAccountDb 6 | BankAccountDb.sql 7 | Data Source=(localdb)\MSSQLLocalDB;Integrated Security=True;Persist Security Info=False;Pooling=False;MultipleActiveResultSets=False;Connect Timeout=60;Encrypt=False;TrustServerCertificate=True 8 | 1 9 | True 10 | True 11 | 12 | -------------------------------------------------------------------------------- /src/code-listings/lesson-35/sample-solution/BankAccountDb/BankAccountDb.refactorlog: -------------------------------------------------------------------------------- 1 |  2 | 3 | -------------------------------------------------------------------------------- /src/code-listings/lesson-35/sample-solution/BankAccountDb/Operation.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE [dbo].[Operation] 2 | ( 3 | [OperationId] INT NOT NULL PRIMARY KEY, 4 | [Description] VARCHAR(32) NOT NULL 5 | ) 6 | -------------------------------------------------------------------------------- /src/code-listings/lesson-35/sample-solution/Core/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/code-listings/lesson-35/sample-solution/Core/Domain.fs: -------------------------------------------------------------------------------- 1 | namespace Capstone6.Domain 2 | 3 | open System 4 | 5 | type BankOperation = Deposit | Withdraw 6 | 7 | /// A customer of the bank. 8 | type Customer = { Name : string } 9 | /// An account held at the bank. 10 | type Account = { AccountId : Guid; Owner : Customer; Balance : decimal } 11 | /// A single transaction that has occurred. 12 | type Transaction = { Timestamp : DateTime; Operation : BankOperation; Amount : decimal } 13 | 14 | /// Represents a bank account that is known to be in credit. 15 | type CreditAccount = CreditAccount of Account 16 | /// A bank account which can either be in credit or overdrawn. 17 | type RatedAccount = 18 | /// Represents an account that is known to be in credit. 19 | | InCredit of Account:CreditAccount 20 | /// Represents an account that is known to be overdrawn. 21 | | Overdrawn of Account:Account 22 | member internal this.GetField getter = 23 | match this with 24 | | InCredit (CreditAccount account) -> getter account 25 | | Overdrawn account -> getter account 26 | /// Gets the current balance of the account. 27 | member this.Balance = this.GetField(fun a -> a.Balance) -------------------------------------------------------------------------------- /src/code-listings/lesson-35/sample-solution/Core/FileRepository.fs: -------------------------------------------------------------------------------- 1 | module internal Capstone6.FileRepository 2 | 3 | open Capstone6.Domain 4 | open System.IO 5 | open System 6 | open Newtonsoft.Json 7 | 8 | let private accountsPath = 9 | let path = @"accounts" 10 | Directory.CreateDirectory path |> ignore 11 | path 12 | let private tryFindAccountFolder owner = 13 | let folders = Directory.EnumerateDirectories(accountsPath, sprintf "%s_*" owner) |> Seq.toList 14 | match folders with 15 | | [] -> None 16 | | folder :: _ -> Some(DirectoryInfo(folder).Name) 17 | 18 | let private buildPath(owner, accountId:Guid) = sprintf @"%s\%s_%O" accountsPath owner accountId 19 | 20 | let private loadTransactions (folder:string) = 21 | let owner, accountId = 22 | let parts = folder.Split '_' 23 | parts.[0], Guid.Parse parts.[1] 24 | accountId, buildPath(owner, accountId) 25 | |> Directory.EnumerateFiles 26 | |> Seq.map (fun path -> JsonConvert.DeserializeObject(File.ReadAllText path)) 27 | 28 | /// Finds all transactions from disk for specific owner. 29 | let tryFindTransactionsOnDisk = tryFindAccountFolder >> Option.map loadTransactions 30 | 31 | /// Logs to the file system 32 | let writeTransaction accountId owner transaction = 33 | let path = buildPath(owner, accountId) 34 | path |> Directory.CreateDirectory |> ignore 35 | let filePath = sprintf "%s/%d.json" path (transaction.Timestamp.ToFileTimeUtc()) 36 | let line = transaction |> JsonConvert.SerializeObject 37 | File.WriteAllText(filePath, line) -------------------------------------------------------------------------------- /src/code-listings/lesson-35/sample-solution/Core/FodyWeavers.xml: -------------------------------------------------------------------------------- 1 |  2 | 3 | -------------------------------------------------------------------------------- /src/code-listings/lesson-35/sample-solution/Core/Scratchpad.fsx: -------------------------------------------------------------------------------- 1 | #load "Domain.fs" 2 | #load "Operations.fs" 3 | #r @"..\packages\FSharp.Data.SqlClient.1.8.2\lib\net40\FSharp.Data.SqlClient.dll" 4 | #load "SqlRepository.fs" 5 | 6 | open Capstone6.Operations 7 | open Capstone6.Domain 8 | open System 9 | open FSharp.Data 10 | 11 | // Copied from SqlRepository.fs, this allows you to test out the queries and commands in isolation. 12 | let [] Conn = @"Data Source=(localdb)\MSSQLLocalDB;Database=BankAccountDb;Integrated Security=True;Connect Timeout=60" 13 | type AccountsDb = SqlProgrammabilityProvider 14 | type GetAccountId = SqlCommandProvider<"SELECT TOP 1 AccountId FROM dbo.Account WHERE Owner = @owner", Conn, SingleRow = true> 15 | type FindTransactions = SqlCommandProvider<"SELECT Timestamp, OperationId, Amount FROM dbo.AccountTransaction WHERE AccountId = @accountId", Conn> 16 | type FindTransactionsByOwner = SqlCommandProvider<"SELECT a.AccountId, at.Timestamp, at.OperationId, at.Amount FROM dbo.Account a LEFT JOIN dbo.AccountTransaction at on a.AccountId = at.AccountId WHERE Owner = @owner", Conn> 17 | type DbOperations = SqlEnumProvider<"SELECT Description, OperationId FROM dbo.Operation", Conn> 18 | 19 | // Get an accountId and then associated transactions. Note that I'm using .Value to "unwrap" the 20 | // optional accountId. This is unsafe and should NEVER be done in "real" code; use either pattern 21 | // matching or Option.map. I'm using it here as (a) this is a demo script, and (b) the database 22 | // is primed with an account that I know about. 23 | let accountId = GetAccountId.Create(Conn).Execute("isaac") 24 | let transactions = FindTransactions.Create(Conn).Execute(accountId.Value) |> Seq.toArray -------------------------------------------------------------------------------- /src/code-listings/lesson-35/sample-solution/Core/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/code-listings/lesson-35/sample-solution/WpfClient/App.xaml: -------------------------------------------------------------------------------- 1 |  6 | 7 | 8 | 9 | 10 | -------------------------------------------------------------------------------- /src/code-listings/lesson-35/sample-solution/WpfClient/App.xaml.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Configuration; 4 | using System.Data; 5 | using System.Linq; 6 | using System.Windows; 7 | 8 | namespace Capstone6 9 | { 10 | /// 11 | /// Interaction logic for App.xaml 12 | /// 13 | public partial class App : Application 14 | { 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/code-listings/lesson-35/sample-solution/WpfClient/BankOperationConverter.cs: -------------------------------------------------------------------------------- 1 | using Capstone6.Domain; 2 | using System; 3 | using System.Globalization; 4 | using System.Windows.Data; 5 | 6 | namespace Capstone6.Converters 7 | { 8 | public class BankOperationConverter : IValueConverter 9 | { 10 | public Object Convert(Object value, Type targetType, Object parameter, CultureInfo culture) 11 | { 12 | var bankOperation = value as BankOperation; 13 | if (bankOperation == null) 14 | return "Unknown"; 15 | else if (bankOperation.IsWithdraw) 16 | return "Withdraw"; 17 | else 18 | return "Deposit"; 19 | } 20 | 21 | public Object ConvertBack(Object value, Type targetType, Object parameter, CultureInfo culture) 22 | { 23 | throw new NotImplementedException(); 24 | } 25 | } 26 | 27 | 28 | } 29 | -------------------------------------------------------------------------------- /src/code-listings/lesson-35/sample-solution/WpfClient/Command.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Windows.Input; 3 | 4 | namespace Capstone6 5 | { 6 | public class Command : ICommand 7 | { 8 | private readonly Action command; 9 | private readonly Func canExecute; 10 | private readonly Func> tryParse; 11 | 12 | public event EventHandler CanExecuteChanged; 13 | 14 | public Command(Action command, Func> tryParse, Func canExecute = null) 15 | { 16 | this.command = command; 17 | this.tryParse = tryParse; 18 | this.canExecute = canExecute ?? (() => true); 19 | } 20 | 21 | public void Refresh() 22 | { 23 | CanExecuteChanged?.Invoke(this, EventArgs.Empty); 24 | } 25 | 26 | public Boolean CanExecute(Object parameter) 27 | { 28 | return canExecute(); 29 | } 30 | 31 | public void Execute(Object parameter) 32 | { 33 | var result = tryParse(parameter); 34 | if (result.Item1) 35 | { 36 | command(result.Item2); 37 | this.Refresh(); 38 | } 39 | } 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/code-listings/lesson-35/sample-solution/WpfClient/FodyWeavers.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/code-listings/lesson-35/sample-solution/WpfClient/MainWindow.xaml.cs: -------------------------------------------------------------------------------- 1 | using System.Windows; 2 | 3 | namespace Capstone6 4 | { 5 | /// 6 | /// Interaction logic for MainWindow.xaml 7 | /// 8 | public partial class MainWindow : Window 9 | { 10 | public MainWindow() 11 | { 12 | InitializeComponent(); 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /src/code-listings/lesson-35/sample-solution/WpfClient/Properties/Settings.Designer.cs: -------------------------------------------------------------------------------- 1 | //------------------------------------------------------------------------------ 2 | // 3 | // This code was generated by a tool. 4 | // Runtime Version:4.0.30319.42000 5 | // 6 | // Changes to this file may cause incorrect behavior and will be lost if 7 | // the code is regenerated. 8 | // 9 | //------------------------------------------------------------------------------ 10 | 11 | namespace Capstone6.Properties { 12 | 13 | 14 | [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] 15 | [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "14.0.0.0")] 16 | internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { 17 | 18 | private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); 19 | 20 | public static Settings Default { 21 | get { 22 | return defaultInstance; 23 | } 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /src/code-listings/lesson-35/sample-solution/WpfClient/Properties/Settings.settings: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /src/code-listings/lesson-35/sample-solution/WpfClient/app.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/code-listings/lesson-35/sample-solution/WpfClient/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /src/code-listings/lesson-37/FSharpWeb/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | module FSharpWeb.AssemblyInfo 2 | open System.Reflection 3 | open System.Runtime.CompilerServices 4 | 5 | 6 | [] 7 | [] 8 | [] 9 | [] 10 | [] 11 | [] 12 | [] 13 | 14 | // The assembly version has the format {Major}.{Minor}.{Build}.{Revision} 15 | 16 | [] 17 | 18 | //[] 19 | //[] 20 | 21 | () 22 | 23 | -------------------------------------------------------------------------------- /src/code-listings/lesson-37/FSharpWeb/Startup.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpWeb 2 | 3 | open Owin 4 | open System.Web.Http 5 | open Newtonsoft.Json.Serialization 6 | 7 | [] 8 | type Startup() = 9 | 10 | static member RegisterWebApi(config: HttpConfiguration) = 11 | // Configure routing 12 | config.MapHttpAttributeRoutes() 13 | 14 | // Configure serialization 15 | config.Formatters.Remove(config.Formatters.XmlFormatter) |> ignore 16 | config.Formatters.JsonFormatter.SerializerSettings.ContractResolver <- DefaultContractResolver() 17 | 18 | member __.Configuration(builder: IAppBuilder) = 19 | let config = new HttpConfiguration() 20 | Startup.RegisterWebApi(config) 21 | builder.UseWebApi(config) |> ignore 22 | 23 | -------------------------------------------------------------------------------- /src/code-listings/lesson-37/FSharpWeb/Web.config: -------------------------------------------------------------------------------- 1 |  2 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /src/code-listings/lesson-37/FSharpWeb/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/code-listings/lesson-37/FSharpWeb/readme.txt: -------------------------------------------------------------------------------- 1 | Registry additions have been made in order to provide you the best web development experience. See http://bloggemdano.blogspot.com/2013/11/adding-new-items-to-pure-f-aspnet.html for more information. -------------------------------------------------------------------------------- /src/code-listings/lesson-37/SuaveApp/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/code-listings/lesson-37/SuaveApp/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | namespace SuaveApp.AssemblyInfo 2 | 3 | open System.Reflection 4 | open System.Runtime.CompilerServices 5 | open System.Runtime.InteropServices 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [] 11 | [] 12 | [] 13 | [] 14 | [] 15 | [] 16 | [] 17 | [] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [] 23 | 24 | // The following GUID is for the ID of the typelib if this project is exposed to COM 25 | [] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [] 37 | [] 38 | [] 39 | 40 | do 41 | () -------------------------------------------------------------------------------- /src/code-listings/lesson-37/SuaveApp/Program.fs: -------------------------------------------------------------------------------- 1 | open Newtonsoft.Json 2 | open Suave 3 | open Suave.Filters 4 | open Suave.Operators 5 | open System.Text 6 | open Suave.Successful 7 | 8 | /// Convert a result to a JSON-enabled WebPart 9 | let asJson mapper = 10 | Suave.Json.mapJsonWith 11 | (fun b -> JsonConvert.DeserializeObject<'TIn>(Encoding.UTF8.GetString b)) 12 | (JsonConvert.SerializeObject >> Encoding.UTF8.GetBytes) 13 | mapper 14 | 15 | /// Converts a result to a WebPart 16 | let asResponse res = 17 | match res with 18 | | Some (Controllers.Success result) -> OK (result |> JsonConvert.SerializeObject) 19 | | Some (Controllers.Failure errorMessage) -> Suave.RequestErrors.BAD_REQUEST errorMessage 20 | | None -> Suave.RequestErrors.NOT_FOUND "" 21 | 22 | /// Wraps the getAnimal function 23 | let getAnimal name ctx = async { 24 | let! animal = Controllers.AnimalsRepository.getAnimal name 25 | return! asResponse animal ctx } 26 | 27 | let app logger = 28 | choose [ 29 | GET >=> 30 | choose [ 31 | path "/api/animals" >=> (Controllers.AnimalsRepository.getAll |> asJson) 32 | pathScan "/api/animals/%s" getAnimal ] 33 | Suave.RequestErrors.NOT_FOUND "" ] 34 | >=> log logger logFormat 35 | 36 | [] 37 | let main _ = 38 | let logger = Logging.LiterateConsoleTarget(Array.empty, Logging.LogLevel.Debug) 39 | let config = Suave.Web.defaultConfig.withLogger logger 40 | Suave.Web.startWebServer config (app logger) 41 | 0 42 | -------------------------------------------------------------------------------- /src/code-listings/lesson-37/SuaveApp/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/code-listings/lesson-37/WebConsole/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/code-listings/lesson-37/WebConsole/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | namespace WebConsole.AssemblyInfo 2 | 3 | open System.Reflection 4 | open System.Runtime.CompilerServices 5 | open System.Runtime.InteropServices 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [] 11 | [] 12 | [] 13 | [] 14 | [] 15 | [] 16 | [] 17 | [] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [] 23 | 24 | // The following GUID is for the ID of the typelib if this project is exposed to COM 25 | [] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [] 37 | [] 38 | [] 39 | 40 | do 41 | () -------------------------------------------------------------------------------- /src/code-listings/lesson-37/WebConsole/Controller.fs: -------------------------------------------------------------------------------- 1 | namespace Controllers 2 | 3 | open System.Web.Http 4 | 5 | type Animal = { Name : string; Species : string } 6 | 7 | [] 8 | type AnimalsController() = 9 | inherit ApiController() 10 | 11 | [] 12 | member __.Get() = [ { Name = "Fido"; Species = "Dog" }; { Name = "Felix"; Species = "Cat" } ] -------------------------------------------------------------------------------- /src/code-listings/lesson-37/WebConsole/Program.fs: -------------------------------------------------------------------------------- 1 | open Microsoft.Owin.Hosting 2 | open Newtonsoft.Json.Serialization 3 | open Owin 4 | open System 5 | open System.Web.Http 6 | 7 | [] 8 | type Startup() = 9 | static member RegisterWebApi(config: HttpConfiguration) = 10 | // Configure routing 11 | config.MapHttpAttributeRoutes() 12 | 13 | // Configure serialization 14 | config.Formatters.Remove(config.Formatters.XmlFormatter) |> ignore 15 | config.Formatters.JsonFormatter.SerializerSettings.ContractResolver <- DefaultContractResolver() 16 | 17 | member __.Configuration(builder: IAppBuilder) = 18 | let config = new HttpConfiguration() 19 | Startup.RegisterWebApi(config) 20 | builder.UseWebApi(config) |> ignore 21 | 22 | [] 23 | let main _ = 24 | use app = WebApp.Start(url = "http://localhost:9000/") 25 | printfn "Listening on localhost:9000!" 26 | Console.ReadLine() |> ignore 27 | 28 | 0 // return an integer exit code 29 | 30 | -------------------------------------------------------------------------------- /src/code-listings/lesson-37/WebConsole/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | -------------------------------------------------------------------------------- /src/code-listings/lesson-38/FSharpWeb.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25420.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "FSharpWeb", "FSharpWeb\FSharpWeb.fsproj", "{AD519605-CD3B-47D4-8C6F-36DA05567C01}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {AD519605-CD3B-47D4-8C6F-36DA05567C01}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {AD519605-CD3B-47D4-8C6F-36DA05567C01}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {AD519605-CD3B-47D4-8C6F-36DA05567C01}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {AD519605-CD3B-47D4-8C6F-36DA05567C01}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /src/code-listings/lesson-38/FSharpWeb/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | module FSharpWeb.AssemblyInfo 2 | open System.Reflection 3 | open System.Runtime.CompilerServices 4 | 5 | 6 | [] 7 | [] 8 | [] 9 | [] 10 | [] 11 | [] 12 | [] 13 | 14 | // The assembly version has the format {Major}.{Minor}.{Build}.{Revision} 15 | 16 | [] 17 | 18 | //[] 19 | //[] 20 | 21 | () 22 | 23 | -------------------------------------------------------------------------------- /src/code-listings/lesson-38/FSharpWeb/Startup.fs: -------------------------------------------------------------------------------- 1 | namespace FSharpWeb 2 | 3 | open Owin 4 | open System.Web.Http 5 | open Newtonsoft.Json.Serialization 6 | open Swashbuckle.Application 7 | 8 | [] 9 | type Startup() = 10 | 11 | static member RegisterWebApi(config: HttpConfiguration) = 12 | // Configure routing 13 | config.MapHttpAttributeRoutes() 14 | 15 | // Configure serialization 16 | config.Formatters.Remove(config.Formatters.XmlFormatter) |> ignore 17 | config.Formatters.JsonFormatter.SerializerSettings.ContractResolver <- DefaultContractResolver() 18 | 19 | member __.Configuration(builder: IAppBuilder) = 20 | let config = new HttpConfiguration() 21 | Startup.RegisterWebApi(config) 22 | config 23 | .EnableSwagger(fun config -> 24 | let path = sprintf @"%s\bin\FSharpWeb.XML" System.AppDomain.CurrentDomain.BaseDirectory 25 | config.IncludeXmlComments path 26 | config.SingleApiVersion("v1", "Animals") |> ignore) 27 | .EnableSwaggerUi() |> ignore 28 | builder.UseWebApi(config) |> ignore 29 | 30 | -------------------------------------------------------------------------------- /src/code-listings/lesson-38/FSharpWeb/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/code-listings/lesson-38/FSharpWeb/readme.txt: -------------------------------------------------------------------------------- 1 | Registry additions have been made in order to provide you the best web development experience. See http://bloggemdano.blogspot.com/2013/11/adding-new-items-to-pure-f-aspnet.html for more information. -------------------------------------------------------------------------------- /src/code-listings/lesson-39/Capstone7.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25420.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Core", "Core\Core.fsproj", "{5194054C-0572-4E34-9D53-9748B6641595}" 7 | EndProject 8 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Web", "Web\Web.fsproj", "{AD519605-CD3B-47D4-8C6F-36DA05567C01}" 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 | {5194054C-0572-4E34-9D53-9748B6641595}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {5194054C-0572-4E34-9D53-9748B6641595}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {5194054C-0572-4E34-9D53-9748B6641595}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {5194054C-0572-4E34-9D53-9748B6641595}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {AD519605-CD3B-47D4-8C6F-36DA05567C01}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {AD519605-CD3B-47D4-8C6F-36DA05567C01}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {AD519605-CD3B-47D4-8C6F-36DA05567C01}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {AD519605-CD3B-47D4-8C6F-36DA05567C01}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | EndGlobal 29 | -------------------------------------------------------------------------------- /src/code-listings/lesson-39/Core/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/code-listings/lesson-39/Core/Domain.fs: -------------------------------------------------------------------------------- 1 | namespace Capstone7.Domain 2 | 3 | open System 4 | open System.ComponentModel.DataAnnotations 5 | 6 | type BankOperation = Deposit | Withdraw 7 | 8 | /// A customer of the bank. 9 | type Customer = { Name : string } 10 | /// An account held at the bank. 11 | type Account = { AccountId : Guid; Owner : Customer; [] Balance : decimal } 12 | /// A single transaction that has occurred. 13 | type Transaction = { Timestamp : DateTime; Operation : BankOperation; Amount : decimal } 14 | 15 | /// Represents a bank account that is known to be in credit. 16 | type CreditAccount = CreditAccount of Account 17 | /// A bank account which can either be in credit or overdrawn. 18 | type RatedAccount = 19 | /// Represents an account that is known to be in credit. 20 | | InCredit of Account:CreditAccount 21 | /// Represents an account that is known to be overdrawn. 22 | | Overdrawn of Account:Account 23 | member internal this.GetField getter = 24 | match this with 25 | | InCredit (CreditAccount account) -> getter account 26 | | Overdrawn account -> getter account 27 | /// Gets the current balance of the account. 28 | member this.Balance = this.GetField(fun a -> a.Balance) -------------------------------------------------------------------------------- /src/code-listings/lesson-39/Core/FileRepository.fs: -------------------------------------------------------------------------------- 1 | module internal Capstone7.FileRepository 2 | 3 | open Capstone7.Domain 4 | open System.IO 5 | open System 6 | open Newtonsoft.Json 7 | 8 | let private accountsPath = 9 | let path = @"accounts" 10 | Directory.CreateDirectory path |> ignore 11 | path 12 | let private tryFindAccountFolder owner = 13 | let folders = Directory.EnumerateDirectories(accountsPath, sprintf "%s_*" owner) |> Seq.toList 14 | match folders with 15 | | [] -> None 16 | | folder :: _ -> Some(DirectoryInfo(folder).Name) 17 | 18 | let private buildPath(owner, accountId:Guid) = sprintf @"%s\%s_%O" accountsPath owner accountId 19 | 20 | let private loadTransactions (folder:string) = 21 | let owner, accountId = 22 | let parts = folder.Split '_' 23 | parts.[0], Guid.Parse parts.[1] 24 | accountId, buildPath(owner, accountId) 25 | |> Directory.EnumerateFiles 26 | |> Seq.map (fun path -> JsonConvert.DeserializeObject(File.ReadAllText path)) 27 | 28 | /// Finds all transactions from disk for specific owner. 29 | let tryFindTransactionsOnDisk = tryFindAccountFolder >> Option.map loadTransactions 30 | 31 | /// Logs to the file system 32 | let writeTransaction accountId owner transaction = 33 | let path = buildPath(owner, accountId) 34 | path |> Directory.CreateDirectory |> ignore 35 | let filePath = sprintf "%s/%d.json" path (transaction.Timestamp.ToFileTimeUtc()) 36 | let line = transaction |> JsonConvert.SerializeObject 37 | File.WriteAllText(filePath, line) -------------------------------------------------------------------------------- /src/code-listings/lesson-39/Core/Scratchpad.fsx: -------------------------------------------------------------------------------- 1 | #load "Domain.fs" 2 | #load "Operations.fs" 3 | 4 | open Capstone7.Operations 5 | open Capstone7.Domain 6 | open System 7 | -------------------------------------------------------------------------------- /src/code-listings/lesson-39/Core/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/code-listings/lesson-39/Web/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | module FSharpWeb.AssemblyInfo 2 | open System.Reflection 3 | open System.Runtime.CompilerServices 4 | 5 | 6 | [] 7 | [] 8 | [] 9 | [] 10 | [] 11 | [] 12 | [] 13 | 14 | // The assembly version has the format {Major}.{Minor}.{Build}.{Revision} 15 | 16 | [] 17 | 18 | //[] 19 | //[] 20 | 21 | () 22 | 23 | -------------------------------------------------------------------------------- /src/code-listings/lesson-39/Web/Controller.fs: -------------------------------------------------------------------------------- 1 | namespace Capstone7.Controllers 2 | 3 | open Capstone7.Domain 4 | open Capstone7.Api 5 | open System -------------------------------------------------------------------------------- /src/code-listings/lesson-39/Web/Startup.fs: -------------------------------------------------------------------------------- 1 | namespace Web 2 | 3 | open Owin 4 | open System.Web.Http 5 | open Newtonsoft.Json.Serialization 6 | open Swashbuckle.Application 7 | 8 | type Startup() = 9 | static member RegisterWebApi(config: HttpConfiguration) = 10 | // Configure routing 11 | config.MapHttpAttributeRoutes() 12 | 13 | // Configure serialization 14 | config.Formatters.Remove(config.Formatters.XmlFormatter) |> ignore 15 | config.Formatters.JsonFormatter.SerializerSettings.ContractResolver <- DefaultContractResolver() 16 | 17 | member __.Configuration(builder: IAppBuilder) = 18 | let config = new HttpConfiguration() 19 | Startup.RegisterWebApi(config) 20 | builder.UseWebApi(config) |> ignore -------------------------------------------------------------------------------- /src/code-listings/lesson-39/Web/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/code-listings/lesson-39/code-listings.fsx: -------------------------------------------------------------------------------- 1 | #I @"..\..\..\packages" 2 | #r "YamlDotNet/lib/net35/YamlDotNet.dll" 3 | #r "SwaggerProvider/lib/net45/SwaggerProvider.dll" 4 | #r "SwaggerProvider/lib/net45/SwaggerProvider.Runtime.dll" 5 | 6 | open SwaggerProvider 7 | type BankApi = SwaggerProvider<"http://localhost:8080/swagger/docs/v1"> 8 | 9 | let bankApi = BankApi() 10 | 11 | let account = bankApi.BankAccountGetAccount "Isaac" 12 | account.Balance 13 | 14 | bankApi.BankAccountPostTransaction("Isaac", BankApi.TransactionRequest(Operation = "Deposit", Amount = 10.)) -------------------------------------------------------------------------------- /src/code-listings/lesson-39/sample-solution/Capstone7.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25420.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Core", "Core\Core.fsproj", "{5194054C-0572-4E34-9D53-9748B6641595}" 7 | EndProject 8 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "Web", "Web\Web.fsproj", "{AD519605-CD3B-47D4-8C6F-36DA05567C01}" 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 | {5194054C-0572-4E34-9D53-9748B6641595}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 17 | {5194054C-0572-4E34-9D53-9748B6641595}.Debug|Any CPU.Build.0 = Debug|Any CPU 18 | {5194054C-0572-4E34-9D53-9748B6641595}.Release|Any CPU.ActiveCfg = Release|Any CPU 19 | {5194054C-0572-4E34-9D53-9748B6641595}.Release|Any CPU.Build.0 = Release|Any CPU 20 | {AD519605-CD3B-47D4-8C6F-36DA05567C01}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 21 | {AD519605-CD3B-47D4-8C6F-36DA05567C01}.Debug|Any CPU.Build.0 = Debug|Any CPU 22 | {AD519605-CD3B-47D4-8C6F-36DA05567C01}.Release|Any CPU.ActiveCfg = Release|Any CPU 23 | {AD519605-CD3B-47D4-8C6F-36DA05567C01}.Release|Any CPU.Build.0 = Release|Any CPU 24 | EndGlobalSection 25 | GlobalSection(SolutionProperties) = preSolution 26 | HideSolutionNode = FALSE 27 | EndGlobalSection 28 | EndGlobal 29 | -------------------------------------------------------------------------------- /src/code-listings/lesson-39/sample-solution/Core/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/code-listings/lesson-39/sample-solution/Core/Domain.fs: -------------------------------------------------------------------------------- 1 | namespace Capstone7.Domain 2 | 3 | open System 4 | open System.ComponentModel.DataAnnotations 5 | 6 | type BankOperation = Deposit | Withdraw 7 | 8 | /// A customer of the bank. 9 | type Customer = { Name : string } 10 | /// An account held at the bank. 11 | type Account = { AccountId : Guid; Owner : Customer; [] Balance : decimal } 12 | /// A single transaction that has occurred. 13 | type Transaction = { [] Timestamp : DateTime; Operation : BankOperation; [] Amount : decimal } 14 | 15 | /// Represents a bank account that is known to be in credit. 16 | type CreditAccount = CreditAccount of Account 17 | /// A bank account which can either be in credit or overdrawn. 18 | type RatedAccount = 19 | /// Represents an account that is known to be in credit. 20 | | InCredit of Account:CreditAccount 21 | /// Represents an account that is known to be overdrawn. 22 | | Overdrawn of Account:Account 23 | member internal this.GetField getter = 24 | match this with 25 | | InCredit (CreditAccount account) -> getter account 26 | | Overdrawn account -> getter account 27 | /// Gets the current balance of the account. 28 | member this.Balance = this.GetField(fun a -> a.Balance) -------------------------------------------------------------------------------- /src/code-listings/lesson-39/sample-solution/Core/FileRepository.fs: -------------------------------------------------------------------------------- 1 | module internal Capstone7.FileRepository 2 | 3 | open Capstone7.Domain 4 | open System.IO 5 | open System 6 | open Newtonsoft.Json 7 | 8 | let private accountsPath = 9 | let path = @"accounts" 10 | Directory.CreateDirectory path |> ignore 11 | path 12 | let private tryFindAccountFolder owner = 13 | let folders = Directory.EnumerateDirectories(accountsPath, sprintf "%s_*" owner) |> Seq.toList 14 | match folders with 15 | | [] -> None 16 | | folder :: _ -> Some(DirectoryInfo(folder).Name) 17 | 18 | let private buildPath(owner, accountId:Guid) = sprintf @"%s\%s_%O" accountsPath owner accountId 19 | 20 | let private loadTransactions (folder:string) = 21 | let owner, accountId = 22 | let parts = folder.Split '_' 23 | parts.[0], Guid.Parse parts.[1] 24 | accountId, buildPath(owner, accountId) 25 | |> Directory.EnumerateFiles 26 | |> Seq.map (fun path -> JsonConvert.DeserializeObject(File.ReadAllText path)) 27 | 28 | /// Finds all transactions from disk for specific owner. 29 | let tryFindTransactionsOnDisk = tryFindAccountFolder >> Option.map loadTransactions 30 | 31 | /// Logs to the file system 32 | let writeTransaction accountId owner transaction = 33 | let path = buildPath(owner, accountId) 34 | path |> Directory.CreateDirectory |> ignore 35 | let filePath = sprintf "%s/%d.json" path (transaction.Timestamp.ToFileTimeUtc()) 36 | let line = transaction |> JsonConvert.SerializeObject 37 | File.WriteAllText(filePath, line) -------------------------------------------------------------------------------- /src/code-listings/lesson-39/sample-solution/Core/Scratchpad.fsx: -------------------------------------------------------------------------------- 1 | #load "Domain.fs" 2 | #load "Operations.fs" 3 | 4 | open Capstone7.Operations 5 | open Capstone7.Domain 6 | open System 7 | -------------------------------------------------------------------------------- /src/code-listings/lesson-39/sample-solution/Core/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/code-listings/lesson-39/sample-solution/Web/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | module FSharpWeb.AssemblyInfo 2 | open System.Reflection 3 | open System.Runtime.CompilerServices 4 | 5 | 6 | [] 7 | [] 8 | [] 9 | [] 10 | [] 11 | [] 12 | [] 13 | 14 | // The assembly version has the format {Major}.{Minor}.{Build}.{Revision} 15 | 16 | [] 17 | 18 | //[] 19 | //[] 20 | 21 | () 22 | 23 | -------------------------------------------------------------------------------- /src/code-listings/lesson-39/sample-solution/Web/Startup.fs: -------------------------------------------------------------------------------- 1 | namespace Web 2 | 3 | open Owin 4 | open System.Web.Http 5 | open Newtonsoft.Json.Serialization 6 | open Swashbuckle.Application 7 | 8 | type Startup() = 9 | static member RegisterWebApi(config: HttpConfiguration) = 10 | // Configure routing 11 | config.MapHttpAttributeRoutes() 12 | 13 | // Configure serialization 14 | config.Formatters.Remove(config.Formatters.XmlFormatter) |> ignore 15 | config.Formatters.JsonFormatter.SerializerSettings.ContractResolver <- DefaultContractResolver() 16 | 17 | member __.Configuration(builder: IAppBuilder) = 18 | let config = new HttpConfiguration(DependencyResolver = new Capstone7.Controllers.DependencyResolver()) 19 | Startup.RegisterWebApi(config) 20 | config 21 | .EnableSwagger(fun config -> 22 | let path = sprintf @"%s\bin\Web.XML" System.AppDomain.CurrentDomain.BaseDirectory 23 | config.IncludeXmlComments path 24 | config.SingleApiVersion("v1", "Bank Accounts") |> ignore) 25 | .EnableSwaggerUi() |> ignore 26 | builder.UseWebApi(config) |> ignore -------------------------------------------------------------------------------- /src/code-listings/lesson-39/sample-solution/Web/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/code-listings/lesson-39/sample-solution/code-listings.fsx: -------------------------------------------------------------------------------- 1 | #I @"..\..\..\..\packages" 2 | #r "YamlDotNet/lib/net35/YamlDotNet.dll" 3 | #r "SwaggerProvider/lib/net45/SwaggerProvider.dll" 4 | #r "SwaggerProvider/lib/net45/SwaggerProvider.Runtime.dll" 5 | 6 | open SwaggerProvider 7 | type BankApi = SwaggerProvider<"http://localhost:8080/swagger/docs/v1"> 8 | 9 | let bankApi = BankApi() 10 | 11 | let account = bankApi.BankAccountGetAccount "Isaac" 12 | account.Balance -------------------------------------------------------------------------------- /src/code-listings/lesson-40/UnitTesting/UnitTesting.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 14 4 | VisualStudioVersion = 14.0.25420.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{F2A71F9B-5D33-465A-A702-920D77279786}") = "UnitTesting", "UnitTesting\UnitTesting.fsproj", "{7BB3E248-6303-4672-98A1-0A6060EDBC41}" 7 | EndProject 8 | Global 9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 10 | Debug|Any CPU = Debug|Any CPU 11 | Release|Any CPU = Release|Any CPU 12 | EndGlobalSection 13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 14 | {7BB3E248-6303-4672-98A1-0A6060EDBC41}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 15 | {7BB3E248-6303-4672-98A1-0A6060EDBC41}.Debug|Any CPU.Build.0 = Debug|Any CPU 16 | {7BB3E248-6303-4672-98A1-0A6060EDBC41}.Release|Any CPU.ActiveCfg = Release|Any CPU 17 | {7BB3E248-6303-4672-98A1-0A6060EDBC41}.Release|Any CPU.Build.0 = Release|Any CPU 18 | EndGlobalSection 19 | GlobalSection(SolutionProperties) = preSolution 20 | HideSolutionNode = FALSE 21 | EndGlobalSection 22 | EndGlobal 23 | -------------------------------------------------------------------------------- /src/code-listings/lesson-40/UnitTesting/UnitTesting/App.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /src/code-listings/lesson-40/UnitTesting/UnitTesting/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | namespace UnitTesting.AssemblyInfo 2 | 3 | open System.Reflection 4 | open System.Runtime.CompilerServices 5 | open System.Runtime.InteropServices 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [] 11 | [] 12 | [] 13 | [] 14 | [] 15 | [] 16 | [] 17 | [] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [] 23 | 24 | // The following GUID is for the ID of the typelib if this project is exposed to COM 25 | [] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [] 37 | [] 38 | [] 39 | 40 | do 41 | () -------------------------------------------------------------------------------- /src/code-listings/lesson-40/UnitTesting/UnitTesting/BusinessLogic.fs: -------------------------------------------------------------------------------- 1 | // Listing 40.1 2 | module BusinessLogic 3 | 4 | type Employee = { Name : string; Age : int } 5 | type Department = { Name : string; Team : Employee list } 6 | 7 | let isLargeDepartment department = department.Team.Length > 10 8 | let isLessThanTwenty person = person.Age < 20 9 | let isLargeAndYoungTeam department = 10 | department |> isLargeDepartment 11 | && department.Team |> List.forall isLessThanTwenty -------------------------------------------------------------------------------- /src/code-listings/lesson-40/UnitTesting/UnitTesting/BusinessLogicTests.fs: -------------------------------------------------------------------------------- 1 | module ``Business Logic Tests`` 2 | 3 | open BusinessLogic 4 | 5 | open Xunit 6 | 7 | let department = 8 | { Name = "Super Team" 9 | Team = [ for i in 1 .. 15 -> { Name = sprintf "Person %d" i; Age = 19 } ] } 10 | 11 | // Listing 40.2 12 | [] 13 | let ``Large, young teams are correctly identified``() = 14 | Assert.True(department |> isLargeAndYoungTeam) 15 | 16 | // Listing 40.3 17 | let isTrue (b:bool) = Assert.True b 18 | 19 | [] 20 | let ``Custom DSLs are easy to write``() = 21 | department 22 | |> isLargeAndYoungTeam 23 | |> isTrue 24 | 25 | // Listing 40.4 26 | open FsUnit.Xunit 27 | [] 28 | let ``FSUnit makes nice DSLs!``() = 29 | department 30 | |> isLargeAndYoungTeam 31 | |> should equal true 32 | 33 | department.Team.Length 34 | |> should be (greaterThan 10) 35 | 36 | // Listing 40.5 37 | open Swensen.Unquote 38 | [] 39 | let ``Unquote has a simple custom operator for equality``() = 40 | department |> isLargeAndYoungTeam =! true 41 | 42 | // Listing 40.6 43 | [] 44 | let ``Unquote can parse quotations for excellent diagnostics``() = 45 | let emptyTeam = { Name = "Super Team"; Team = [] } 46 | test <@ emptyTeam.Name.StartsWith "D" @> -------------------------------------------------------------------------------- /src/code-listings/lesson-40/UnitTesting/UnitTesting/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/code-listings/lesson-42.fsx: -------------------------------------------------------------------------------- 1 | #r @"..\..\packages\Selenium.WebDriver\lib\net40\WebDriver.dll" 2 | #r @"..\..\packages\canopy\lib\canopy.dll" 3 | open canopy 4 | 5 | chromeDir <- "drivers" 6 | 7 | // Now you try #1 8 | start chrome 9 | url "https://www.manning.com/books/get-programming-with-f-sharp" 10 | 11 | first "#Submit" |> click 12 | click ".cart-button" 13 | click ".btn-primary" 14 | 15 | "#email" << "Fred.Smith@fakemail.com" 16 | elements ".btn-primary" |> Seq.find(fun e -> e.Text = "checkout as a guest") |> click 17 | 18 | "#country" << "United States" 19 | "#firstName" << "Fred" 20 | "#lastName" << "Smith" 21 | "#company" << "Super F# Developers Ltd." 22 | "#address1" << "23 The Street" 23 | "#address2" << "The Town" 24 | "#city" << "The City" 25 | "#USStateSelector" << "CA" 26 | "#zip" << "90210" 27 | "#addressPhone" << "0800 123 456" 28 | 29 | quit() 30 | 31 | // Listing 42.1 32 | once (fun _ -> start chrome) 33 | before (fun _ -> url "https://www.manning.com/books/get-programming-with-f-sharp") 34 | lastly (fun _ -> quit()) 35 | 36 | // Listing 42.2 37 | context "Sample Tests" 38 | reporter <- reporters.LiveHtmlReporter(BrowserStartMode.Chrome, "drivers") :> reporters.IReporter 39 | 40 | test (fun _ -> "#chapter_id_1" == "LESSON 1 THE VISUAL STUDIO EXPERIENCE") 41 | "49 lessons in total" &&& fun _ -> count ".sect1" 49 42 | "There's a web programming unit" &&& fun _ -> ".sect0" *= "UNIT 8: WEB PROGRAMMING" 43 | 44 | run() 45 | 46 | -------------------------------------------------------------------------------- /src/code-listings/lesson-43/Core/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/code-listings/lesson-43/Core/Domain.fs: -------------------------------------------------------------------------------- 1 | namespace Capstone8.Domain 2 | 3 | open System 4 | open System.ComponentModel.DataAnnotations 5 | 6 | type BankOperation = Deposit | Withdraw 7 | 8 | /// A customer of the bank. 9 | type Customer = { Name : string } 10 | /// An account held at the bank. 11 | type Account = { AccountId : Guid; Owner : Customer; [] Balance : decimal } 12 | /// A single transaction that has occurred. 13 | type Transaction = { [] Timestamp : DateTime; Operation : BankOperation; [] Amount : decimal } 14 | 15 | /// Represents a bank account that is known to be in credit. 16 | type CreditAccount = CreditAccount of Account 17 | /// A bank account which can either be in credit or overdrawn. 18 | type RatedAccount = 19 | /// Represents an account that is known to be in credit. 20 | | InCredit of Account:CreditAccount 21 | /// Represents an account that is known to be overdrawn. 22 | | Overdrawn of Account:Account 23 | member internal this.GetField getter = 24 | match this with 25 | | InCredit (CreditAccount account) -> getter account 26 | | Overdrawn account -> getter account 27 | /// Gets the current balance of the account. 28 | member this.Balance = this.GetField(fun a -> a.Balance) -------------------------------------------------------------------------------- /src/code-listings/lesson-43/Core/FileRepository.fs: -------------------------------------------------------------------------------- 1 | module internal Capstone8.FileRepository 2 | 3 | open Capstone8.Domain 4 | open System.IO 5 | open System 6 | open Newtonsoft.Json 7 | 8 | let private accountsPath = 9 | let path = @"accounts" 10 | Directory.CreateDirectory path |> ignore 11 | path 12 | let private tryFindAccountFolder owner = 13 | let folders = Directory.EnumerateDirectories(accountsPath, sprintf "%s_*" owner) |> Seq.toList 14 | match folders with 15 | | [] -> None 16 | | folder :: _ -> Some(DirectoryInfo(folder).Name) 17 | 18 | let private buildPath(owner, accountId:Guid) = sprintf @"%s\%s_%O" accountsPath owner accountId 19 | 20 | let private loadTransactions (folder:string) = 21 | let owner, accountId = 22 | let parts = folder.Split '_' 23 | parts.[0], Guid.Parse parts.[1] 24 | accountId, buildPath(owner, accountId) 25 | |> Directory.EnumerateFiles 26 | |> Seq.map (fun path -> JsonConvert.DeserializeObject(File.ReadAllText path)) 27 | 28 | /// Finds all transactions from disk for specific owner. 29 | let tryFindTransactionsOnDisk = tryFindAccountFolder >> Option.map loadTransactions 30 | 31 | /// Logs to the file system 32 | let writeTransaction accountId owner transaction = 33 | let path = buildPath(owner, accountId) 34 | path |> Directory.CreateDirectory |> ignore 35 | let filePath = sprintf "%s/%d.json" path (transaction.Timestamp.ToFileTimeUtc()) 36 | let line = transaction |> JsonConvert.SerializeObject 37 | File.WriteAllText(filePath, line) -------------------------------------------------------------------------------- /src/code-listings/lesson-43/Core/Scratchpad.fsx: -------------------------------------------------------------------------------- 1 | #load "Domain.fs" 2 | #load "Operations.fs" 3 | 4 | open Capstone8.Operations 5 | open Capstone8.Domain 6 | open System 7 | -------------------------------------------------------------------------------- /src/code-listings/lesson-43/Core/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/code-listings/lesson-43/Tests/ApiTests.fs: -------------------------------------------------------------------------------- 1 | module ``API Tests`` 2 | 3 | open Capstone8.Domain 4 | open Xunit 5 | open Swensen.Unquote 6 | 7 | [] 8 | let ``Creates an account if none exists``() = 9 | async { 10 | let api = createInMemApi() 11 | let! account = api.LoadAccount customer 12 | test <@ account.Balance = 0M @> } 13 | |> Async.RunSynchronously -------------------------------------------------------------------------------- /src/code-listings/lesson-43/Tests/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | namespace Tests.AssemblyInfo 2 | 3 | open System.Reflection 4 | open System.Runtime.CompilerServices 5 | open System.Runtime.InteropServices 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [] 11 | [] 12 | [] 13 | [] 14 | [] 15 | [] 16 | [] 17 | [] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [] 23 | 24 | // The following GUID is for the ID of the typelib if this project is exposed to COM 25 | [] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [] 37 | [] 38 | [] 39 | 40 | do 41 | () -------------------------------------------------------------------------------- /src/code-listings/lesson-43/Tests/Helpers.fs: -------------------------------------------------------------------------------- 1 | [] 2 | module internal Helpers 3 | 4 | open System 5 | open Capstone8.Domain 6 | open Capstone8.Api 7 | 8 | let customer = { Name = "Joe" } 9 | 10 | let createInMemApi() = 11 | let dataStore = ResizeArray() 12 | let save accountId owner transaction = () 13 | let load (owner:string) = async.Return None 14 | buildApi load save 15 | -------------------------------------------------------------------------------- /src/code-listings/lesson-43/Tests/PropertyTests.fs: -------------------------------------------------------------------------------- 1 | module ``Property Tests`` 2 | 3 | open Capstone8.Api 4 | open Capstone8.Domain 5 | open FsCheck 6 | open FsCheck.Xunit 7 | 8 | let isSuccess result = match result with Success _ -> true | Failure _ -> false 9 | 10 | [] 11 | let ``Going under 0 makes the account overdrawn``(PositiveInt startingBalance) = 12 | let startingBalance = decimal startingBalance 13 | true -------------------------------------------------------------------------------- /src/code-listings/lesson-43/Tests/WebTests.fs: -------------------------------------------------------------------------------- 1 | module ``Web Tests`` 2 | 3 | open System 4 | open Xunit 5 | open Swensen.Unquote 6 | open Capstone8.Controllers 7 | open System.Threading 8 | open System.Web.Http 9 | open System.Net.Http 10 | open System.Threading.Tasks 11 | 12 | let createController() = 13 | let api = createInMemApi() 14 | let request = new HttpRequestMessage() 15 | let config = new HttpConfiguration() 16 | new BankAccountController(api, Request = request, Configuration = config) 17 | 18 | let executeRequest (request:IHttpActionResult Task) = 19 | async { 20 | let! request = request |> Async.AwaitTask 21 | return! request.ExecuteAsync(CancellationToken.None) |> Async.AwaitTask } 22 | |> Async.RunSynchronously 23 | -------------------------------------------------------------------------------- /src/code-listings/lesson-43/Tests/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/code-listings/lesson-43/Web/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | module FSharpWeb.AssemblyInfo 2 | open System.Reflection 3 | open System.Runtime.CompilerServices 4 | 5 | 6 | [] 7 | [] 8 | [] 9 | [] 10 | [] 11 | [] 12 | [] 13 | 14 | // The assembly version has the format {Major}.{Minor}.{Build}.{Revision} 15 | 16 | [] 17 | 18 | //[] 19 | //[] 20 | 21 | () 22 | 23 | -------------------------------------------------------------------------------- /src/code-listings/lesson-43/Web/Startup.fs: -------------------------------------------------------------------------------- 1 | namespace Web 2 | 3 | open Owin 4 | open System.Web.Http 5 | open Newtonsoft.Json.Serialization 6 | open Swashbuckle.Application 7 | 8 | type Startup() = 9 | static member RegisterWebApi(config: HttpConfiguration) = 10 | // Configure routing 11 | config.MapHttpAttributeRoutes() 12 | 13 | // Configure serialization 14 | config.Formatters.Remove(config.Formatters.XmlFormatter) |> ignore 15 | config.Formatters.JsonFormatter.SerializerSettings.ContractResolver <- DefaultContractResolver() 16 | 17 | member __.Configuration(builder: IAppBuilder) = 18 | let config = new HttpConfiguration(DependencyResolver = new Capstone8.Controllers.DependencyResolver()) 19 | Startup.RegisterWebApi(config) 20 | config 21 | .EnableSwagger(fun config -> 22 | let path = sprintf @"%s\bin\Web.XML" System.AppDomain.CurrentDomain.BaseDirectory 23 | config.IncludeXmlComments path 24 | config.SingleApiVersion("v1", "Bank Accounts") |> ignore) 25 | .EnableSwaggerUi() |> ignore 26 | builder.UseWebApi(config) |> ignore -------------------------------------------------------------------------------- /src/code-listings/lesson-43/Web/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/code-listings/lesson-43/sample-solution/Core/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/code-listings/lesson-43/sample-solution/Core/Domain.fs: -------------------------------------------------------------------------------- 1 | namespace Capstone8.Domain 2 | 3 | open System 4 | open System.ComponentModel.DataAnnotations 5 | 6 | type BankOperation = Deposit | Withdraw 7 | 8 | /// A customer of the bank. 9 | type Customer = { Name : string } 10 | /// An account held at the bank. 11 | type Account = { AccountId : Guid; Owner : Customer; [] Balance : decimal } 12 | /// A single transaction that has occurred. 13 | type Transaction = { [] Timestamp : DateTime; Operation : BankOperation; [] Amount : decimal } 14 | 15 | /// Represents a bank account that is known to be in credit. 16 | type CreditAccount = CreditAccount of Account 17 | /// A bank account which can either be in credit or overdrawn. 18 | type RatedAccount = 19 | /// Represents an account that is known to be in credit. 20 | | InCredit of Account:CreditAccount 21 | /// Represents an account that is known to be overdrawn. 22 | | Overdrawn of Account:Account 23 | member internal this.GetField getter = 24 | match this with 25 | | InCredit (CreditAccount account) -> getter account 26 | | Overdrawn account -> getter account 27 | /// Gets the current balance of the account. 28 | member this.Balance = this.GetField(fun a -> a.Balance) -------------------------------------------------------------------------------- /src/code-listings/lesson-43/sample-solution/Core/FileRepository.fs: -------------------------------------------------------------------------------- 1 | module internal Capstone8.FileRepository 2 | 3 | open Capstone8.Domain 4 | open System.IO 5 | open System 6 | open Newtonsoft.Json 7 | 8 | let private accountsPath = 9 | let path = @"accounts" 10 | Directory.CreateDirectory path |> ignore 11 | path 12 | let private tryFindAccountFolder owner = 13 | let folders = Directory.EnumerateDirectories(accountsPath, sprintf "%s_*" owner) |> Seq.toList 14 | match folders with 15 | | [] -> None 16 | | folder :: _ -> Some(DirectoryInfo(folder).Name) 17 | 18 | let private buildPath(owner, accountId:Guid) = sprintf @"%s\%s_%O" accountsPath owner accountId 19 | 20 | let private loadTransactions (folder:string) = 21 | let owner, accountId = 22 | let parts = folder.Split '_' 23 | parts.[0], Guid.Parse parts.[1] 24 | accountId, buildPath(owner, accountId) 25 | |> Directory.EnumerateFiles 26 | |> Seq.map (fun path -> JsonConvert.DeserializeObject(File.ReadAllText path)) 27 | 28 | /// Finds all transactions from disk for specific owner. 29 | let tryFindTransactionsOnDisk = tryFindAccountFolder >> Option.map loadTransactions 30 | 31 | /// Logs to the file system 32 | let writeTransaction accountId owner transaction = 33 | let path = buildPath(owner, accountId) 34 | path |> Directory.CreateDirectory |> ignore 35 | let filePath = sprintf "%s/%d.json" path (transaction.Timestamp.ToFileTimeUtc()) 36 | let line = transaction |> JsonConvert.SerializeObject 37 | File.WriteAllText(filePath, line) -------------------------------------------------------------------------------- /src/code-listings/lesson-43/sample-solution/Core/Scratchpad.fsx: -------------------------------------------------------------------------------- 1 | #load "Domain.fs" 2 | #load "Operations.fs" 3 | 4 | open Capstone8.Operations 5 | open Capstone8.Domain 6 | open System 7 | -------------------------------------------------------------------------------- /src/code-listings/lesson-43/sample-solution/Core/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /src/code-listings/lesson-43/sample-solution/Tests/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | namespace Tests.AssemblyInfo 2 | 3 | open System.Reflection 4 | open System.Runtime.CompilerServices 5 | open System.Runtime.InteropServices 6 | 7 | // General Information about an assembly is controlled through the following 8 | // set of attributes. Change these attribute values to modify the information 9 | // associated with an assembly. 10 | [] 11 | [] 12 | [] 13 | [] 14 | [] 15 | [] 16 | [] 17 | [] 18 | 19 | // Setting ComVisible to false makes the types in this assembly not visible 20 | // to COM components. If you need to access a type in this assembly from 21 | // COM, set the ComVisible attribute to true on that type. 22 | [] 23 | 24 | // The following GUID is for the ID of the typelib if this project is exposed to COM 25 | [] 26 | 27 | // Version information for an assembly consists of the following four values: 28 | // 29 | // Major Version 30 | // Minor Version 31 | // Build Number 32 | // Revision 33 | // 34 | // You can specify all the values or you can default the Build and Revision Numbers 35 | // by using the '*' as shown below: 36 | // [] 37 | [] 38 | [] 39 | 40 | do 41 | () -------------------------------------------------------------------------------- /src/code-listings/lesson-43/sample-solution/Tests/PropertyTests.fs: -------------------------------------------------------------------------------- 1 | module ``Property Tests`` 2 | 3 | open Capstone8.Api 4 | open FsCheck 5 | open FsCheck.Xunit 6 | open Capstone8.Domain 7 | open ``API Tests`` 8 | 9 | let isSuccess result = match result with Success _ -> true | Failure _ -> false 10 | 11 | [] 12 | let ``Going under 0 makes the account overdrawn``(PositiveInt startingBalance) = 13 | let startingBalance = decimal startingBalance 14 | async { 15 | let _, api = createInMemApi() 16 | do! customer |> api.Deposit startingBalance |> Async.Ignore 17 | let! account = customer |> api.Withdraw (startingBalance + 1M) 18 | match account with 19 | | Success (Overdrawn _) -> return true 20 | | Success (InCredit _) -> return false 21 | | Failure _ -> return false } 22 | |> Async.RunSynchronously 23 | 24 | [] 25 | let ``Withdraws fail if overdrawn``(PositiveInt withdrawAmount)= 26 | let withdrawAmount = decimal withdrawAmount 27 | async { 28 | let _, api = createInMemApi() 29 | do! customer |> api.Withdraw withdrawAmount |> Async.Ignore 30 | let! accountResult = customer |> api.Withdraw 1M 31 | let! account = api.LoadAccount customer 32 | match accountResult with 33 | | Failure _ when account.Balance = -withdrawAmount -> return true 34 | | _ -> return false } 35 | |> Async.RunSynchronously 36 | -------------------------------------------------------------------------------- /src/code-listings/lesson-43/sample-solution/Tests/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /src/code-listings/lesson-43/sample-solution/Web/AssemblyInfo.fs: -------------------------------------------------------------------------------- 1 | module FSharpWeb.AssemblyInfo 2 | open System.Reflection 3 | open System.Runtime.CompilerServices 4 | 5 | 6 | [] 7 | [] 8 | [] 9 | [] 10 | [] 11 | [] 12 | [] 13 | 14 | // The assembly version has the format {Major}.{Minor}.{Build}.{Revision} 15 | 16 | [] 17 | 18 | //[] 19 | //[] 20 | 21 | () 22 | 23 | -------------------------------------------------------------------------------- /src/code-listings/lesson-43/sample-solution/Web/Startup.fs: -------------------------------------------------------------------------------- 1 | namespace Web 2 | 3 | open Owin 4 | open System.Web.Http 5 | open Newtonsoft.Json.Serialization 6 | open Swashbuckle.Application 7 | 8 | type Startup() = 9 | static member RegisterWebApi(config: HttpConfiguration) = 10 | // Configure routing 11 | config.MapHttpAttributeRoutes() 12 | 13 | // Configure serialization 14 | config.Formatters.Remove(config.Formatters.XmlFormatter) |> ignore 15 | config.Formatters.JsonFormatter.SerializerSettings.ContractResolver <- DefaultContractResolver() 16 | 17 | member __.Configuration(builder: IAppBuilder) = 18 | let config = new HttpConfiguration(DependencyResolver = new Capstone8.Controllers.DependencyResolver()) 19 | Startup.RegisterWebApi(config) 20 | config 21 | .EnableSwagger(fun config -> 22 | let path = sprintf @"%s\bin\Web.XML" System.AppDomain.CurrentDomain.BaseDirectory 23 | config.IncludeXmlComments path 24 | config.SingleApiVersion("v1", "Bank Accounts") |> ignore) 25 | .EnableSwaggerUi() |> ignore 26 | builder.UseWebApi(config) |> ignore -------------------------------------------------------------------------------- /src/code-listings/lesson-43/sample-solution/Web/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/code-listings/lesson-43/sample-solution/code-listings.fsx: -------------------------------------------------------------------------------- 1 | #I @"..\..\..\..\packages" 2 | #r "YamlDotNet/lib/net35/YamlDotNet.dll" 3 | #r "SwaggerProvider/lib/net45/SwaggerProvider.dll" 4 | #r "SwaggerProvider/lib/net45/SwaggerProvider.Runtime.dll" 5 | 6 | open SwaggerProvider 7 | type BankApi = SwaggerProvider<"http://localhost:8080/swagger/docs/v1"> 8 | 9 | let bankApi = BankApi() 10 | 11 | let account = bankApi.BankAccountGetAccount "Isaac" 12 | account.Balance --------------------------------------------------------------------------------