Tres - Step 3
We'll start out, as we did with Dos, with adding the RethinkDB dependency to project.json
's dependencies
section:
1:
|
|
dotnet restore
installs it as usual.
Configurable Connection
Since Tres is more-or-less a C#-to-F# conversion from Dos, we'll use the same data-config.json
file, in the
root of the project:
1: 2: 3: 4: |
|
We'll also add it to project.json
, just below the list of files to compile:
1: 2: 3: |
|
Now, we'll create a file Data.fs
to hold what we had in the Data
directory in the prior two solutions. It should
be added to the build order after Entities.fs
and before HomeModule.fs
. We'll start this file off with our
DataConfig
implementation:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: 17: 18: 19: 20: 21: 22: 23: 24: 25: 26: 27: |
|
This should be familiar at this point; we're using a record type instead of a class, and the CreateConnection
function utilizes the sequence style from Dos, just inlined as a computation expression (more on those in a bit).
We also see Seq.fold
, which takes the parameters in pretty much the opposite order of LINQ's Aggregate
; instead of
[collection].Aggregate([initial-state], [folder-func])
, it's Seq.fold [folder-func] [initial-state] [collection]
(which we're piping in with the |>
operator).
The upcast
is new. Notice that CreateConnection
is typed as IConnection
; what's returned from the connection
builder is a Connection
. In most cases, F# requires an implementation to be explicitly cast to the interface it is
claiming to implement. In our case, we want IConnection
(vs. IDisposable
, which it also implements). There are
two ways to do this; if the type can be inferred, as it can be here (because we've explicitly said what our return type
should be), you can use upcast
. Alternately, the last line of that function could read
(bldr.Connect()) :> IConnection
. (I tend to prefer upcast
when possible.)
At this point, we need to take a detour through the land of asynchronous processing. Uno and Dos both used
async/await to perform the RethinkDB calls, utilizing the Task
-based async introduced in .NET 4.5. F#'s approach to
asynchrony is different, but there are a few functions that provide the interoperability we need. F# also uses an
async
computation expression to construct these. The most important difference, for our purposes here, is that F#
Async
instances are not automatically started the way a Task
is in the C# world.
And, a quick detour from the detour - I promised there would be more on computation expressions. These are expressions
that utilize an expression builder to declaratively create workflows within code. They typically operate in a
specialized context; we've seen seq
, we're about to see async
, but there are many other uses as well. Within a
computation expression, let
and do
have their same familiar behavior, and return
returns a value; however,
let!
, do!
, and return!
call into the builder to manipulate the specialized context. A complete education in
computation expressions is outside of our scope; F# for Fun and Profit has an
excellent series on them.
Before we see our first async
computation expression, though, we need to make it be able to handle Task
s as well as
F# async. The code for Extensions.fs
is below. I won't delve too far into it at this point, but trust that what it
does is let us say let! x = someTask
and it works just like it was F# async. (This code is in the AutoOpen
ed
module Tres.Extensions
.)
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: |
|
Add Extensions.fs
to the build order at the top. With those in place, we are now ready to return to Data.fs
and
our startup code. Before we move on, review
the startup code from Dos;
it has a main driver method at the top, with several methods below to perform each step. Our F# code structure will be
somewhat inverted from this; as you generally cannot call forward into a source file, the "driver" code will be the
last code in the function, and the other code above it.
The Table.cs
static class with constants is brought over as a module (below the data configuration code). The
RequireQualifiedAccess
attribute means that Table
cannot be open
ed; this prevents us from possibly providing an
unintended version of the identifier Post
(for example).
1: 2: 3: 4: 5: 6: 7: 8: |
|
Extensions are defined differently in F#. Whereas C# uses a static class and methods where the first parameter has
this
before it, F# uses a module for the definitions (usually AutoOpen
ed, so they're visible when the enclosing
namespace is opened), and a type
declaration. Here's the top of ours (below the table module):
1: 2: 3: 4: 5: |
|
Rather than go through the entire file, let's just look at a representative example. Here is the code to check for table existence in C#:
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: |
|
Now, here's what it looks like in F#:
1: 2: 3: 4: 5: 6: 7: 8: |
|
The more interesting differences:
-
In C#,
existing
is awaited; in F#, we uselet!
within theasync
computation expression to accomplish the same thing. - In C#, we defined a
List<string>
that we filled with our table names; in F#, we inlined astring list
. -
In C#, we have a nice imperative loop that iterates over each table, checks to see whether it is in the list of
tables from the server, and creates it if it is not. In F#, we declare that the list should be filtered to only names
not occurring in the list (
List.filter
); then that each of those names should be turned into anAsync
that will create the table when it's run (List.map
); then, that the list should be iterated, passing each item intoAsync.RunSynchronously
(List.iter
). - In C#, the return type of the method is
Task
; in F#, the type ofcheckTables
isAsync<unit>
. -
When the C#
CheckTables
method call returns, the work has already been done. When the F#checkTables
function returns, it returns an async workflow that is ready to be started.
The last 5 lines of the EstablishEnvironment
extension method look like this:
1: 2: 3: 4: 5: |
|
There are a few interesting observations here as well:
do!
is the equivalent oflet!
, except that we don't care about the result.-
As we saw above,
checkTables
returns an async workflow; yet, we'redo!
ing it in yet another async workflow; this is perfectly acceptable. If you've ever added async/await to a C# application, usually at a lower layer, and noticed how async and await bubble up to the top layer - that's a similar concept to what we have here. -
EstablishEnvironment
's return type isAsync<unit>
. It still hasn't run anything at this point; it has merely assembled an asynchronous workflow that will do all of our environment checks once it is run.
Dependency Injection
We'll do the same thing we did for Dos - override DefaultNancyBootstrapper
and register our connection there.
We'll do all of this in App.fs
. The first part, above the definition for type Startup()
:
1: 2: 3: 4: 5: 6: 7: 8: 9: |
|
Ah ha! There's where we finally run our async workflow! Now, again, we need to modify Startup
(just below where
we put this code) to use this new bootstrapper.
1: 2: |
|
At this point, once dotnet run
displays the "listening on port 5000" message, we should be able to look at
RethinkDB's O2F3
database, tables, and indexes, just as we could for Uno and Dos.
{Hostname: string;
Port: int;
AuthKey: string;
Timeout: int;
Database: string;}
member CreateConnection : unit -> IConnection
static member FromJson : json:string -> DataConfig
Full name: Tres.DataConfig
val string : value:'T -> string
Full name: Microsoft.FSharp.Core.Operators.string
--------------------
type string = System.String
Full name: Microsoft.FSharp.Core.string
val int : value:'T -> int (requires member op_Explicit)
Full name: Microsoft.FSharp.Core.Operators.int
--------------------
type int = int32
Full name: Microsoft.FSharp.Core.int
--------------------
type int<'Measure> = int
Full name: Microsoft.FSharp.Core.int<_>
Full name: Tres.DataConfig.CreateConnection
member RunAsync<'T> : term:ReqlAst * globalOpts:obj * cancelToken:CancellationToken -> Task<obj>
member RunAtomAsync<'T> : term:ReqlAst * globalOpts:obj * cancelToken:CancellationToken -> Task<'T>
member RunCursorAsync<'T> : term:ReqlAst * globalOpts:obj * cancelToken:CancellationToken -> Task<Cursor<'T>>
member RunNoReply : term:ReqlAst * globalOpts:obj -> unit
member RunResultAsync<'T> : term:ReqlAst * globalOpts:obj * cancelToken:CancellationToken -> Task<'T>
Full name: RethinkDb.Driver.Net.IConnection
val seq : sequence:seq<'T> -> seq<'T>
Full name: Microsoft.FSharp.Core.Operators.seq
--------------------
type seq<'T> = System.Collections.Generic.IEnumerable<'T>
Full name: Microsoft.FSharp.Collections.seq<_>
member CheckOpen : unit -> unit
member ClientEndPoint : IPEndPoint
member Close : ?shouldNoReplyWait:bool -> unit
member Db : string
member Dispose : unit -> unit
member HasError : bool
member Hostname : string
member NoReplyWait : unit -> unit
member NoReplyWaitAsync : ?cancelToken:CancellationToken -> Task
member Open : bool
...
nested type Builder
Full name: RethinkDb.Driver.Net.Connection
new : unit -> Builder
member AuthKey : key:string -> Builder
member Connect : unit -> Connection
member ConnectAsync : unit -> Task<Connection>
member Db : val:string -> Builder
member Hostname : val:string -> Builder
member Port : val:int -> Builder
member Timeout : val:int -> Builder
member User : user:string * password:string -> Builder
Full name: RethinkDb.Driver.Net.Connection.Builder
from Microsoft.FSharp.Collections
Full name: Microsoft.FSharp.Collections.Seq.fold
type RethinkDB =
inherit TopLevel
new : unit -> RethinkDB
member Connection : unit -> Builder
member ConnectionPool : unit -> Builder
static val R : RethinkDB
Full name: RethinkDb.Driver.RethinkDB
--------------------
RethinkDB() : unit
Full name: Tres.DataConfig.FromJson
static val True : string
static val False : string
static val Null : string
static val Undefined : string
static val PositiveInfinity : string
static val NegativeInfinity : string
static val NaN : string
static member DefaultSettings : Func<JsonSerializerSettings> with get, set
static member DeserializeAnonymousType<'T> : value:string * anonymousTypeObject:'T -> 'T + 1 overload
static member DeserializeObject : value:string -> obj + 7 overloads
...
Full name: Newtonsoft.Json.JsonConvert
JsonConvert.DeserializeObject(value: string) : obj
JsonConvert.DeserializeObject<'T>(value: string, settings: JsonSerializerSettings) : 'T
JsonConvert.DeserializeObject<'T>(value: string, [<System.ParamArray>] converters: JsonConverter []) : 'T
JsonConvert.DeserializeObject(value: string, type: System.Type) : obj
JsonConvert.DeserializeObject(value: string, settings: JsonSerializerSettings) : obj
JsonConvert.DeserializeObject(value: string, type: System.Type, settings: JsonSerializerSettings) : obj
JsonConvert.DeserializeObject(value: string, type: System.Type, [<System.ParamArray>] converters: JsonConverter []) : obj
type AutoOpenAttribute =
inherit Attribute
new : unit -> AutoOpenAttribute
new : path:string -> AutoOpenAttribute
member Path : string
Full name: Microsoft.FSharp.Core.AutoOpenAttribute
--------------------
new : unit -> AutoOpenAttribute
new : path:string -> AutoOpenAttribute
private new : unit -> AsyncBuilder
member Bind : computation:Async<'T> * binder:('T -> Async<'U>) -> Async<'U>
member Combine : computation1:Async<unit> * computation2:Async<'T> -> Async<'T>
member Delay : generator:(unit -> Async<'T>) -> Async<'T>
member For : sequence:seq<'T> * body:('T -> Async<unit>) -> Async<unit>
member Return : value:'T -> Async<'T>
member ReturnFrom : computation:Async<'T> -> Async<'T>
member TryFinally : computation:Async<'T> * compensation:(unit -> unit) -> Async<'T>
member TryWith : computation:Async<'T> * catchHandler:(exn -> Async<'T>) -> Async<'T>
member Using : resource:'T * binder:('T -> Async<'U>) -> Async<'U> (requires 'T :> IDisposable)
...
Full name: Microsoft.FSharp.Control.AsyncBuilder
Full name: Tres.ExampleExtensions.Bind
An extension method that overloads the standard 'Bind' of the 'async' builder. The new overload awaits on
a standard .NET task
type Task =
new : action:Action -> Task + 7 overloads
member AsyncState : obj
member ContinueWith : continuationAction:Action<Task> -> Task + 9 overloads
member CreationOptions : TaskCreationOptions
member Dispose : unit -> unit
member Exception : AggregateException
member Id : int
member IsCanceled : bool
member IsCompleted : bool
member IsFaulted : bool
...
Full name: System.Threading.Tasks.Task
--------------------
type Task<'TResult> =
inherit Task
new : function:Func<'TResult> -> Task<'TResult> + 7 overloads
member ContinueWith : continuationAction:Action<Task<'TResult>> -> Task + 9 overloads
member Result : 'TResult with get, set
static member Factory : TaskFactory<'TResult>
Full name: System.Threading.Tasks.Task<_>
--------------------
Task(action: System.Action) : unit
Task(action: System.Action, cancellationToken: System.Threading.CancellationToken) : unit
Task(action: System.Action, creationOptions: TaskCreationOptions) : unit
Task(action: System.Action<obj>, state: obj) : unit
Task(action: System.Action, cancellationToken: System.Threading.CancellationToken, creationOptions: TaskCreationOptions) : unit
Task(action: System.Action<obj>, state: obj, cancellationToken: System.Threading.CancellationToken) : unit
Task(action: System.Action<obj>, state: obj, creationOptions: TaskCreationOptions) : unit
Task(action: System.Action<obj>, state: obj, cancellationToken: System.Threading.CancellationToken, creationOptions: TaskCreationOptions) : unit
--------------------
Task(function: System.Func<'TResult>) : unit
Task(function: System.Func<'TResult>, cancellationToken: System.Threading.CancellationToken) : unit
Task(function: System.Func<'TResult>, creationOptions: TaskCreationOptions) : unit
Task(function: System.Func<obj,'TResult>, state: obj) : unit
Task(function: System.Func<'TResult>, cancellationToken: System.Threading.CancellationToken, creationOptions: TaskCreationOptions) : unit
Task(function: System.Func<obj,'TResult>, state: obj, cancellationToken: System.Threading.CancellationToken) : unit
Task(function: System.Func<obj,'TResult>, state: obj, creationOptions: TaskCreationOptions) : unit
Task(function: System.Func<obj,'TResult>, state: obj, cancellationToken: System.Threading.CancellationToken, creationOptions: TaskCreationOptions) : unit
type Async
static member AsBeginEnd : computation:('Arg -> Async<'T>) -> ('Arg * AsyncCallback * obj -> IAsyncResult) * (IAsyncResult -> 'T) * (IAsyncResult -> unit)
static member AwaitEvent : event:IEvent<'Del,'T> * ?cancelAction:(unit -> unit) -> Async<'T> (requires delegate and 'Del :> Delegate)
static member AwaitIAsyncResult : iar:IAsyncResult * ?millisecondsTimeout:int -> Async<bool>
static member AwaitTask : task:Task -> Async<unit>
static member AwaitTask : task:Task<'T> -> Async<'T>
static member AwaitWaitHandle : waitHandle:WaitHandle * ?millisecondsTimeout:int -> Async<bool>
static member CancelDefaultToken : unit -> unit
static member Catch : computation:Async<'T> -> Async<Choice<'T,exn>>
static member FromBeginEnd : beginAction:(AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg:'Arg1 * beginAction:('Arg1 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg1:'Arg1 * arg2:'Arg2 * beginAction:('Arg1 * 'Arg2 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromBeginEnd : arg1:'Arg1 * arg2:'Arg2 * arg3:'Arg3 * beginAction:('Arg1 * 'Arg2 * 'Arg3 * AsyncCallback * obj -> IAsyncResult) * endAction:(IAsyncResult -> 'T) * ?cancelAction:(unit -> unit) -> Async<'T>
static member FromContinuations : callback:(('T -> unit) * (exn -> unit) * (OperationCanceledException -> unit) -> unit) -> Async<'T>
static member Ignore : computation:Async<'T> -> Async<unit>
static member OnCancel : interruption:(unit -> unit) -> Async<IDisposable>
static member Parallel : computations:seq<Async<'T>> -> Async<'T []>
static member RunSynchronously : computation:Async<'T> * ?timeout:int * ?cancellationToken:CancellationToken -> 'T
static member Sleep : millisecondsDueTime:int -> Async<unit>
static member Start : computation:Async<unit> * ?cancellationToken:CancellationToken -> unit
static member StartAsTask : computation:Async<'T> * ?taskCreationOptions:TaskCreationOptions * ?cancellationToken:CancellationToken -> Task<'T>
static member StartChild : computation:Async<'T> * ?millisecondsTimeout:int -> Async<Async<'T>>
static member StartChildAsTask : computation:Async<'T> * ?taskCreationOptions:TaskCreationOptions -> Async<Task<'T>>
static member StartImmediate : computation:Async<unit> * ?cancellationToken:CancellationToken -> unit
static member StartWithContinuations : computation:Async<'T> * continuation:('T -> unit) * exceptionContinuation:(exn -> unit) * cancellationContinuation:(OperationCanceledException -> unit) * ?cancellationToken:CancellationToken -> unit
static member SwitchToContext : syncContext:SynchronizationContext -> Async<unit>
static member SwitchToNewThread : unit -> Async<unit>
static member SwitchToThreadPool : unit -> Async<unit>
static member TryCancelled : computation:Async<'T> * compensation:(OperationCanceledException -> unit) -> Async<'T>
static member CancellationToken : Async<CancellationToken>
static member DefaultCancellationToken : CancellationToken
Full name: Microsoft.FSharp.Control.Async
--------------------
type Async<'T>
Full name: Microsoft.FSharp.Control.Async<_>
Full name: Microsoft.FSharp.Core.ExtraTopLevelOperators.async
An extension method that overloads the standard 'Bind' of the 'async' builder. The new overload awaits on
a standard .NET task which does not commpute a value
member AsyncBuilder.Bind : t:Task<'T> * f:('T -> Async<'R>) -> Async<'R>
An extension method that overloads the standard 'Bind' of the 'async' builder. The new overload awaits on
a standard .NET task
member AsyncBuilder.Bind : computation:Async<'T> * binder:('T -> Async<'U>) -> Async<'U>
static member Async.AwaitTask : task:Task<'T> -> Async<'T>
Full name: Tres.ExampleExtensions.Bind
An extension method that overloads the standard 'Bind' of the 'async' builder. The new overload awaits on
a standard .NET task which does not commpute a value
Full name: Microsoft.FSharp.Core.unit
Full name: Tres.ExampleExtensions.ReturnFrom
type RequireQualifiedAccessAttribute =
inherit Attribute
new : unit -> RequireQualifiedAccessAttribute
Full name: Microsoft.FSharp.Core.RequireQualifiedAccessAttribute
--------------------
new : unit -> RequireQualifiedAccessAttribute
type Table =
inherit ReqlExpr
new : arg:obj -> Table + 2 overloads
member Config : unit -> Config
member Get : expr:obj -> Get
member GetAll : [<ParamArray>] exprs:obj[] -> GetAll + 2 overloads
member GetIntersecting : expr:obj -> GetIntersecting
member GetNearest : expr:obj -> GetNearest
member Grant : expr:obj * exprA:obj -> Grant
member IndexCreate : expr:obj -> IndexCreate + 4 overloads
member IndexDrop : expr:obj -> IndexDrop
member IndexList : unit -> IndexList
...
Full name: RethinkDb.Driver.Ast.Table
--------------------
Table(arg: obj) : unit
Table(args: Model.Arguments) : unit
Table(args: Model.Arguments, optargs: Model.OptArgs) : unit
Full name: Tres.Table.Category
Full name: Tres.Table.Comment
Full name: Tres.Table.Page
Full name: Tres.Table.Post
Full name: Tres.Table.User
Full name: Tres.Table.WebLog
Full name: Tres.DataExtensions.EstablishEnvironment
Full name: Microsoft.FSharp.Collections.list<_>
module List
from Microsoft.FSharp.Collections
--------------------
type List<'T> =
| ( [] )
| ( :: ) of Head: 'T * Tail: 'T list
interface IEnumerable
interface IEnumerable<'T>
member GetSlice : startIndex:int option * endIndex:int option -> 'T list
member Head : 'T
member IsEmpty : bool
member Item : index:int -> 'T with get
member Length : int
member Tail : 'T list
static member Cons : head:'T * tail:'T list -> 'T list
static member Empty : 'T list
Full name: Microsoft.FSharp.Collections.List<_>
Full name: Microsoft.FSharp.Collections.List.filter
Full name: Microsoft.FSharp.Core.Operators.not
Full name: Microsoft.FSharp.Collections.List.contains
Full name: Microsoft.FSharp.Collections.List.map
Full name: Microsoft.FSharp.Collections.List.iter
type TresBootstrapper =
inherit DefaultNancyBootstrapper
new : unit -> TresBootstrapper
member Configure : app:IApplicationBuilder -> unit
override ConfigureApplicationContainer : container:TinyIoCContainer -> unit
Full name: Tres.TresBootstrapper
--------------------
new : unit -> TresBootstrapper
type DefaultNancyBootstrapper =
inherit NancyBootstrapperWithRequestContainerBase<TinyIoCContainer>
new : unit -> DefaultNancyBootstrapper
member GetEnvironment : unit -> INancyEnvironment
static val DefaultAutoRegisterIgnoredAssemblies : IEnumerable<Func<Assembly, bool>>
Full name: Nancy.DefaultNancyBootstrapper
--------------------
DefaultNancyBootstrapper() : unit
Full name: Tres.TresBootstrapper.ConfigureApplicationContainer
static member AppendAllLines : path:string * contents:IEnumerable<string> -> unit + 1 overload
static member AppendAllText : path:string * contents:string -> unit + 1 overload
static member AppendText : path:string -> StreamWriter
static member Copy : sourceFileName:string * destFileName:string -> unit + 1 overload
static member Create : path:string -> FileStream + 3 overloads
static member CreateText : path:string -> StreamWriter
static member Decrypt : path:string -> unit
static member Delete : path:string -> unit
static member Encrypt : path:string -> unit
static member Exists : path:string -> bool
...
Full name: System.IO.File
File.ReadAllText(path: string, encoding: System.Text.Encoding) : string
(+0 other overloads)
TinyIoc.TinyIoCContainer.Register<'RegisterType (requires reference type)>() : TinyIoc.TinyIoCContainer.RegisterOptions
(+0 other overloads)
TinyIoc.TinyIoCContainer.Register<'RegisterType (requires reference type)>(factory: System.Func<TinyIoc.TinyIoCContainer,TinyIoc.NamedParameterOverloads,'RegisterType>) : TinyIoc.TinyIoCContainer.RegisterOptions
(+0 other overloads)
TinyIoc.TinyIoCContainer.Register<'RegisterType,'RegisterImplementation (requires reference type and reference type and 'RegisterImplementation :> 'RegisterType)>(instance: 'RegisterImplementation) : TinyIoc.TinyIoCContainer.RegisterOptions
(+0 other overloads)
TinyIoc.TinyIoCContainer.Register<'RegisterType (requires reference type)>(instance: 'RegisterType) : TinyIoc.TinyIoCContainer.RegisterOptions
(+0 other overloads)
TinyIoc.TinyIoCContainer.Register<'RegisterType,'RegisterImplementation (requires reference type and reference type and 'RegisterImplementation :> 'RegisterType)>(name: string) : TinyIoc.TinyIoCContainer.RegisterOptions
(+0 other overloads)
TinyIoc.TinyIoCContainer.Register<'RegisterType (requires reference type)>(name: string) : TinyIoc.TinyIoCContainer.RegisterOptions
(+0 other overloads)
TinyIoc.TinyIoCContainer.Register(registerType: System.Type) : TinyIoc.TinyIoCContainer.RegisterOptions
(+0 other overloads)
TinyIoc.TinyIoCContainer.Register<'RegisterType (requires reference type)>(factory: System.Func<TinyIoc.TinyIoCContainer,TinyIoc.NamedParameterOverloads,'RegisterType>, name: string) : TinyIoc.TinyIoCContainer.RegisterOptions
(+0 other overloads)
TinyIoc.TinyIoCContainer.Register<'RegisterType,'RegisterImplementation (requires reference type and reference type and 'RegisterImplementation :> 'RegisterType)>(instance: 'RegisterImplementation, name: string) : TinyIoc.TinyIoCContainer.RegisterOptions
(+0 other overloads)
Full name: Microsoft.FSharp.Core.Operators.ignore
Full name: Tres.TresBootstrapper.Configure
member ApplicationServices : IServiceProvider with get, set
member Build : unit -> RequestDelegate
member New : unit -> IApplicationBuilder
member Properties : IDictionary<string, obj>
member ServerFeatures : IFeatureCollection
member Use : middleware:Func<RequestDelegate, RequestDelegate> -> IApplicationBuilder
Full name: Microsoft.AspNetCore.Builder.IApplicationBuilder
(extension) IApplicationBuilder.UseOwin(pipeline: System.Action<System.Action<System.Func<System.Func<System.Collections.Generic.IDictionary<string,obj>,System.Threading.Tasks.Task>,System.Func<System.Collections.Generic.IDictionary<string,obj>,System.Threading.Tasks.Task>>>>) : IApplicationBuilder
(extension) System.Action.UseNancy(?options: NancyOptions) : System.Action<System.Func<System.Func<System.Collections.Generic.IDictionary<string,obj>,System.Threading.Tasks.Task>,System.Func<System.Collections.Generic.IDictionary<string,obj>,System.Threading.Tasks.Task>>>