Dos - Step 3
This step will follow the same general path as Uno, but we'll also iterate on what we made there to bring some of the code into a more functional style.
project.json
Changes
We only need to add the RethinkDB package to what we had at the end of step 2 (under dependencies
):
1:
|
|
Run dotnet restore
to pull in that dependency. Also, we'll bring across the entire Data
directory we created in
Uno during this step. We'll be able to use Table.cs
and EnvironmentExtensions.cs
as is (except for changing
the namespace to Dos.Data
).
Configurable Connection
Since appsettings.json
is a .NET Core thing, we will not use it here. We can still use JSON to configure our
connection, though; here's the data-config.json
file:
1: 2: 3: 4: |
|
To support this, we'll need to change a couple of other things. First, in our DataConfig
file, we'll add the
following using statement and static method:
1: 2: 3: |
|
Now we can create our configuration from this JSON once we read it in. We're also going to modify the
CreateConnection()
method; we'll also add some more using
s and a support property.
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: |
|
If this is the first time you've seen code like this, you may be thinking "Why would we do something like that?" We're
moving from an imperative style, like we used in Uno, to a more declarative style. Blocks
is an enumeration (or
sequence) where each item yielded takes a connection builder, and returns either the original connection builder it
received or one that has been modified with a configuration parameter. If you didn't understand that last sentence,
look at the code, then read it again; understanding this structure will pay dividends once we're knee-deep in F#.
Once you understand the Blocks
property, take a look at the CreateConnection()
method. This uses the LINQ method
Aggregate
, which takes two parameters: an initial state; and a function that will be given the current state and each
item, and will return the "current" state after that item has been processed. If that makes no sense, imagine you had
a sequence exSeq
with the letters "A", "B", and "C". If you were to run
var str = exSeq.Aggregate("", (state, letter) => String.Format("{0},{1}", state, letter));
, str
would hold the
string ",A,B,C". The "initial state" is simply the starting value; but, every iteration must return a value of that
same type.
Hopefully Aggregate
is making sense at this point. Taking a forward-looking side trip - we're going to see it, with
a different parameter order, as the fold
function in our F# code. You've likely heard the term "map/reduce" - this
describes a process where, given a data collection, you can transform it into a shape you need (map) and distill that
data into the answer you need (reduce). (Yes, purists, this is a bit of a simplification.) F# provides map
and
reduce
implementations for several collection types; however, reduce
cannot produce a type different from that of
the underlying collection - fold
is what does that.
Back from our side trip, what this code does is:
-
Seeds
Aggregate
withRethinkDB.R.Connection()
, which is an instance ofConnection.Builder
(Builder
is a nested type withinRethinkDb.Driver.Net.Connection
; theusing static
makes it visible the way we've used it here.) -
Loops through each item of our enumeration. Since each item is a
Func<Builder, Builder>
, we pass the item the current builder; it returns a builder that may have been further configured ("aggregating" our configuration). -
Once the
Aggregate
has completed, we're ready to callConnect()
on our connection builder, and return that from our method.
Seeing a more functional style with C# should help when we start seeing F# collections.
Dependency Injection
With Nancy, if you want to add forks to the SDHP, you have to provide a bootstrapper that will handle the startup code.
For most purposes, the best way is to simply override DefaultNancyBootstrapper
; that way, any code you don't provide
will use the default, and you can call base
methods from your overridden ones, so all the SDHP magic continues to
work.
Here's the custom bootstrapper we'll use:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: |
|
This looks very similar to the code from the ASP.NET Core implementation, with the exception of how we're getting the
configuration. We're all done except for two minor fixes. First, we need to tell Nancy to use this bootstrapper
instead of the default. This is Startup.cs
:
1: 2: |
|
Finally, we need to specify that our data-config.json
file should be copied to the output directory; otherwise, it
will just sit on the hard drive while you scratch your head trying to figure out why your application can't connect.
(Ask me how I know...) This change is in project.json
, just under the emitEntryPoint
declaration (included here
for context):
1: 2: 3: 4: |
|
At this point, you should be able to dotnet run
and, once the console says that it's listening, you should be able to
see the database, tables, and indexes in the O2F2
database.