Skip to content
This repository was archived by the owner on Apr 3, 2024. It is now read-only.

Commit fe8ada7

Browse files
Merge remaining changes from master
2 parents 1541873 + b501b2a commit fe8ada7

26 files changed

Lines changed: 3038 additions & 3 deletions

.gitignore

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -251,5 +251,5 @@ paket-files/
251251
.idea/
252252
*.sln.iml
253253

254-
# Documents
255-
dox/output
254+
# Documentation output
255+
dox/output/

.paket/paket.bootstrapper.exe

39 KB
Binary file not shown.

README.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# objects () |> functions
22

3+
_**NOTE:** Once tooling has finalized around .NET Standard 2, I will be bringing the currently done steps up to
4+
current, then continue._
5+
36
## What
47

58
This repository will track the development of a rudimentary multi-site blog platform, in parallel, in 4 different
@@ -36,4 +39,4 @@ _This learning is not my primary occupation, so the pace may be slow; my hope is
3639
## The Steps
3740

3841
The plan is laid out, and will be documented as we go along, on
39-
[the wiki](https://github.com/danieljsummers/FromObjectsToFunctions/wiki).
42+
[GitHub Pages](https://danieljsummers.github.io/FromObjectsToFunctions/).

dox/content/index.md

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# objects () |> functions
2+
3+
This page serves / will serve as the table of contents for each step of the development process. Below the TOC is a table with comparisons among the 4 projects.
4+
5+
**[Step 1](step1)** - Hello World
6+
Establish a web request response with the least possible / complex code
7+
8+
**[Step 2](step2)** - Data Model
9+
Create the persistent types we'll use for our application
10+
11+
**[Step 3](step3)** - RethinkDB Connection
12+
Configure and prepare a RethinkDB connection for use in our application
13+
14+
**Step 4** - Framework Setup
15+
Create folders / conventions for our application and load some dummy data
16+
17+
**Step 5** - Routes
18+
Establish the URLs that our application will recognize
19+
20+
**Step 6** - Views
21+
Implement the web pages by which our application will display its information
22+
23+
**Step 7** - Log In
24+
Users are important in this application!
25+
26+
**Step 8** - Page Publishing and Editing
27+
Create and edit pages
28+
29+
**Step 9** - Post Publishing and Editing
30+
Finally, we can write some posts!
31+
32+
**Step 10** - Categories and Tags
33+
Handle lists of posts by category or tag
34+
35+
**Step 11** - RSS Feeds
36+
Provide RSS and Atom feeds for our content
37+
38+
**Step n** - may be added later (yay, scope creep!)
39+
40+
| item | Uno | Dos | Tres | Quatro |
41+
| --- | --- | --- | --- | --- |
42+
| Language | C# | C# | F# | F# |
43+
| Server | Kestrel | Kestrel | Kestrel | Suave |
44+
| Framework | ASP.NET Core | Nancy | Nancy | Freya |
45+
| Views | Razor | SSVE | SSVE | SSVE |

dox/content/step1/dos.md

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
### Dos - Step 1
2+
3+
For this project, we'll also start with `project.json`, bringing in the dependencies we'll need.
4+
5+
[lang=text]
6+
"dependencies": {
7+
"Microsoft.AspNetCore.Owin": "1.0.0",
8+
"Microsoft.AspNetCore.Server.Kestrel": "1.0.0",
9+
"Nancy": "2.0.0-barneyrubble"
10+
},
11+
12+
Nancy strives to provide a Super-Duper-Happy-Path (SDHP), where all you have to do is follow their conventions, and everything will "just work." (You can also configure every aspect of it; it's only opinionated in its defaults.) One of these conventions is that the controllers inherit from `NancyModule`, and when they do, no further configuration is required. So, we create the `Modules` directory, and add `HomeModule.cs`, which looks like this:
13+
14+
[lang=csharp]
15+
namespace Dos.Modules
16+
{
17+
using Nancy;
18+
19+
public class HomeModule : NancyModule
20+
{
21+
public HomeModule() : base()
22+
{
23+
Get("/", _ => "Hello World from Nancy C#");
24+
}
25+
}
26+
}
27+
28+
Since we'll be hosting this with Kestrel (via OWIN), we still need a `Startup.cs`, though its `Configure()` method looks a bit different:
29+
30+
[lang=csharp]
31+
public void Configure(IApplicationBuilder app) =>
32+
app.UseOwin(x => x.UseNancy());
33+
34+
(We need to add a using statement for `Nancy.Owin` so that the `UseNancy()` method is visible.)
35+
36+
The `App.cs` file is identical to the one from Uno.
37+
38+
[Back to Step 1](../step1)

dox/content/step1/index.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
## Hello World
2+
3+
For "Hello World" in each environment, we'll create an empty .NET Core console application. To view the entire
4+
completed source from this step, see
5+
[the checkpoint for step 1](https://github.com/danieljsummers/FromObjectsToFunctions/tree/step-1).
6+
7+
All projects were created using `dotnet new -t console -l C#` (or `F#`).
8+
9+
**Uno** - [In Depth](uno.html)
10+
11+
**Dos** - [In Depth](dos.html)
12+
13+
**Tres** - [In Depth](tres.html)
14+
15+
**Quatro** - [In Depth](quatro.html)

dox/content/step1/quatro.fsx

Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
(*** hide ***)
2+
#r "../../../packages/Freya.Core/lib/net452/Freya.Core.dll"
3+
#r "../../../packages/Freya.Machines.Http/lib/net452/Freya.Machines.Http.dll"
4+
#r "../../../packages/Freya.Routers.Uri.Template/lib/net452/Freya.Routers.Uri.Template.dll"
5+
#r "../../../packages/Freya.Types.Uri.Template/lib/net452/Freya.Types.Uri.Template.dll"
6+
#r "../../../packages/Microsoft.AspNetCore.Hosting/lib/net451/Microsoft.AspNetCore.Hosting.dll"
7+
#r "../../../packages/Microsoft.AspNetCore.Hosting.Abstractions/lib/net451/Microsoft.AspNetCore.Hosting.Abstractions.dll"
8+
#r "../../../packages/Microsoft.AspNetCore.Http.Abstractions/lib/net451/Microsoft.AspNetCore.Http.Abstractions.dll"
9+
#r "../../../packages/Microsoft.AspNetCore.Owin/lib/net451/Microsoft.AspNetCore.Owin.dll"
10+
#r "../../../packages/Microsoft.AspNetCore.Server.Kestrel/lib/net451/Microsoft.AspNetCore.Server.Kestrel.dll"
11+
12+
(**
13+
### Quatro - Step 1
14+
15+
Having [already made the leap to F#](tres.html), we will now do our Hello World in Freya. This is adapted from the
16+
"Getting Started" article on their site.
17+
18+
First up, we'll attack `project.json`. This one will require more modifications\* than the others. Let's start
19+
with the dependencies section:
20+
21+
[lang=text]
22+
"dependencies": {
23+
"Freya": "3.0.0-rc01",
24+
"Microsoft.AspNetCore.Owin": "1.0.0",
25+
"Microsoft.AspNetCore.Server.Kestrel": "1.0.0",
26+
"Microsoft.NETCore.Portable.Compatibility": "1.0.1"
27+
},
28+
29+
Freya should be self-explanatory, and we've seen the Owin and Kestrel imports before. The new one is the last one, and
30+
this package provides fill-ins for some types that used to be defined in `mscorlib` (the big library-to-rule-them-all
31+
that went away between the full .NET framework and .NET Core).
32+
33+
The frameworks sections needs some attention as well:
34+
35+
[lang=text]
36+
"frameworks": {
37+
"netcoreapp1.0": {
38+
"dependencies": {
39+
"Microsoft.NETCore.App": {
40+
"type": "platform",
41+
"version": "1.0.1"
42+
},
43+
"Microsoft.FSharp.Core.netcore": "1.0.0-alpha-160831"
44+
},
45+
"imports": [
46+
"portable-net45+win8+dnxcore50",
47+
"portable-net45+win8",
48+
"net452",
49+
"dnxcore50"
50+
]
51+
}
52+
},
53+
54+
These imports allow us to use some Freya dependencies that target Portable Class Libraries (PCLs) that are supported by
55+
.NET Core. Finally, while we're there, change "Program.fs" to "App.fs" in the compiled file list, as we'll rename
56+
`Program.fs` to `App.fs` to remain consistent among the projects.
57+
58+
Now, for `App.fs`:
59+
*)
60+
namespace Quatro
61+
62+
open Freya.Core
63+
open Freya.Machines.Http
64+
open Freya.Routers.Uri.Template
65+
open Microsoft.AspNetCore.Builder
66+
open Microsoft.AspNetCore.Hosting
67+
(**
68+
`Freya.Core` gives us the `freya` computation expression, which we will use for the main part of our request handling.
69+
`Freya.Machines.Http` provides the `freyaMachine` computation expression, which allows us to define our
70+
request-response. `Freya.Routers.Uri.Template` provides the `freyaRouter` computation expression, where we assign an
71+
HTTP machine to a URL route pattern.
72+
73+
Continuing on...
74+
*)
75+
module App =
76+
let hello =
77+
freya {
78+
return Represent.text "Hello World from Freya"
79+
}
80+
81+
let machine =
82+
freyaMachine {
83+
handleOk hello
84+
}
85+
86+
let router =
87+
freyaRouter {
88+
resource "/" machine
89+
}
90+
(**
91+
This code uses the three expressions described above to define the response (hard-coded for now), the machine that uses
92+
it for its OK response, and the route that uses the machine.
93+
94+
Still within `module App =`...
95+
*)
96+
type Startup () =
97+
member __.Configure (app : IApplicationBuilder) =
98+
let freyaOwin = OwinMidFunc.ofFreya (UriTemplateRouter.Freya router)
99+
app.UseOwin (fun p -> p.Invoke freyaOwin) |> ignore
100+
101+
[<EntryPoint>]
102+
let main _ =
103+
use host = (new WebHostBuilder()).UseKestrel().UseStartup<Startup>().Build()
104+
host.Run()
105+
0
106+
(**
107+
This is the familiar `Startup` class from Tres, except that the `Configure()` method uses the Freya implementation
108+
instead of the Nancy implementation. Notice that the middleware function uses the router as the hook into the
109+
pipeline; that is how we get the OWIN request to be handled by Freya. Notice how much closer to idiomatic F# this code
110+
has become; the only place we had to `ignore` anything was the "seam" where we interoperated with the OWIN library.
111+
112+
`dotnet run` should succeed at this point, and localhost:5000 should display our Hello World message.
113+
114+
[Back to Step 1](../step1)
115+
116+
---
117+
118+
\* - Huge props go to @neoeinstein for documenting these settings in
119+
[this Gist](https://gist.github.com/neoeinstein/66c2c8ace158b3e701e206e172e91f8b). I had not seen the PCL compat
120+
package _or_ an import for "net452" in .NET Core before this.
121+
*)

dox/content/step1/tres.fsx

Lines changed: 108 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
(*** hide ***)
2+
#r "../../../packages/Nancy/lib/net452/Nancy.dll"
3+
#r "../../../packages/Microsoft.AspNetCore.Hosting/lib/net451/Microsoft.AspNetCore.Hosting.dll"
4+
#r "../../../packages/Microsoft.AspNetCore.Hosting.Abstractions/lib/net451/Microsoft.AspNetCore.Hosting.Abstractions.dll"
5+
#r "../../../packages/Microsoft.AspNetCore.Http.Abstractions/lib/net451/Microsoft.AspNetCore.Http.Abstractions.dll"
6+
#r "../../../packages/Microsoft.AspNetCore.Owin/lib/net451/Microsoft.AspNetCore.Owin.dll"
7+
#r "../../../packages/Microsoft.AspNetCore.Server.Kestrel/lib/net451/Microsoft.AspNetCore.Server.Kestrel.dll"
8+
9+
(**
10+
### Tres - Step 1
11+
12+
Here, we're making the leap to F#. The changes to `project.json` for the dependent packages are the same as they were
13+
for [Dos](dos.html). F# projects are historically not split into directories, as compilation order is significant, and
14+
having them in the same directory allows the tooling to ensure that the compilation order is preserved. With the
15+
structure of `project.json`, this is not necessarily a limitation (though the tooling still doesn't support it, as of
16+
this writing), but we'll follow it for our purposes here.
17+
18+
The module is created as `HomeModule.fs` in the project root:
19+
*)
20+
namespace Tres
21+
22+
open Nancy
23+
24+
type HomeModule() as this =
25+
inherit NancyModule()
26+
27+
do
28+
this.Get("/", fun _ -> "Hello World from Nancy F#")
29+
(**
30+
If you look at [Dos](dos.html), you can see how the translation occurred:
31+
32+
- "using" becomes "open"
33+
- F# does not express constructors in the way C# folks are used to seeing them. Parameters to the class are specified
34+
in the type declaration (or a `new` function, which we don't need for our purposes), and then are visible throughout
35+
the class.
36+
- Since we don't have an explicit constructor where we can wire up the `Get()` method call, we accomplish it using a
37+
`do` binding; this is code that will be run every time the class is instantiated. The `as this` at the end of
38+
`type HomeModule()` allows us to use `this` to refer to the current instance; otherwise, `do` cannot see it.
39+
- This also illustrates the syntax differences in defining lambdas between C# and F#. F# uses the `fun` keyword to
40+
indicate an anonymous function. The `_` is used to indicate that we do not care what the parameter is; since this
41+
request doesn't require anything from the `DynamicDictionary` Nancy provides, we don't.
42+
43+
We rename `Program.fs` to `App.fs`, and in this file, we provide the contents from both `Startup.cs` and `App.cs`.
44+
*)
45+
namespace Tres
46+
47+
open Microsoft.AspNetCore.Builder
48+
open Microsoft.AspNetCore.Hosting
49+
open Nancy
50+
open Nancy.Owin
51+
52+
type Startup() =
53+
member this.Configure (app : IApplicationBuilder) =
54+
app.UseOwin (fun x -> x.UseNancy (fun x -> ()) |> ignore) |> ignore
55+
56+
module App =
57+
[<EntryPoint>]
58+
let main argv =
59+
use host = (new WebHostBuilder()).UseKestrel().UseStartup<Startup>().Build()
60+
host.Run()
61+
0
62+
(**
63+
The `Startup` class is exactly the same as the C# version, though it appears much differently. The `UseNancy()` method
64+
returns quite a complex result, but the parameter to the `UseOwin()` method expects an `Action<>`; by definition, this
65+
returns `void`\*. In F#, there is no implicit throwaway of results\**; you must explicitly mark results that should be
66+
ignored. `UseNancy` also expects an `Action<>`, so we end up with an extra lambda and two `ignore`s to accomplish the
67+
same thing.
68+
69+
The `App` module is also new. F# modules can be thought of as static classes (if you use one from C#, that's what they
70+
look like). An F# source file must start with either a namespace or module declaration; also, any code (`let`, `do`,
71+
`member`, etc.) cannot be simply in a namespace. We start with the `Tres` namespace so that our `Startup` class's full
72+
name will be `Tres.Startup`, so we have to define a module for our `let` binding / entry point.
73+
74+
At this point, `dotnet build` will fail. I mentioned compilation order earlier; we've added one file and renamed the
75+
other, but we have yet to tell the compiler about them, or how they should be ordered. Back in `project.json`, look for
76+
the `buildOptions` entry, and replace
77+
78+
[lang=text]
79+
"compile": {
80+
"includeFiles": [
81+
"Program.fs"
82+
]
83+
}
84+
85+
with
86+
87+
[lang=text]
88+
"compile": {
89+
"includeFiles": [
90+
"HomeModule.fs",
91+
"App.fs"
92+
]
93+
}
94+
95+
(In the future, we'll add updating this list to our discipline of creating a new file.)
96+
97+
Now, we can execute `dotnet run`, watch it start, visit localhost:5000, and see our F# message.
98+
99+
[Back to Step 1](../step1)
100+
101+
---
102+
103+
\* The `unit` type in F# is the parallel to this, but there's more to it than just "something else to call `void`."
104+
105+
\** For example, `StringBuilder.Append()` returns the builder so you can chain calls, but it also mutates the builder,
106+
and you don't have to provide a variable assignment for every call. In F#, you would either need to provide that, or
107+
pipe the output (`|>`) to `ignore`.
108+
*)

dox/content/step1/uno.md

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
### Uno - Step 1
2+
3+
_NOTE: While there is a "web" target for C#, it pulls in a lot of files that I'd rather not go through and explain. We
4+
will not be using Entity Framework for anything, and though this application will use some of the Identity features of
5+
ASP.NET Core MVC, we will not be using its membership features. Since all of that is out of scope for this effort, and
6+
all of this is in the "web" template, we won't use it._ 😃
7+
8+
To start, we'll open `project.json` and add the dependencies we'll need:
9+
10+
[lang=text]
11+
"dependencies": {
12+
"Microsoft.AspNetCore.Owin": "1.0.0",
13+
"Microsoft.AspNetCore.Server.Kestrel": "1.0.0"
14+
},
15+
16+
`dotnet restore` fixes up the actual packages. Next, we'll create the `Startup.cs` file. Within its `Configure` method, we'll do a very basic lambda to return a string:
17+
18+
[lang=csharp]
19+
public void Configure(IApplicationBuilder app) =>
20+
app.Run(async context => await context.Response.WriteAsync("Hello World from ASP.NET Core"));
21+
22+
(We put in using statements for `Microsoft.AspNetCore.Builder` to make the `IApplicationBuilder` visible and `Microsoft.AspNetCore.Http` to expose the `WriteAsync()` method on the `Response` object.)
23+
24+
We'll rename `Program.cs` to `App.cs`. (Why? Well - why not?) Then, within the `Main()` method, we'll construct a Kestrel instance and run it.
25+
26+
[lang=csharp]
27+
using (var host = new WebHostBuilder().UseKestrel().UseStartup<Startup>().Build())
28+
{
29+
host.Run();
30+
}
31+
32+
(Most demos don't show the web host wrapped in a using block; it's `IDisposable`, though, so it's a good idea.)
33+
34+
At this point, `dotnet run` should give us a successful startup, and browsing to localhost:5000 returns our greeting.
35+
36+
[Back to Step 1](../step1)

0 commit comments

Comments
 (0)