Tres - Step 1
Here, we're making the leap to F#. Once we ensure that our project file is named Tres.fsproj
, the contents of the file
should be the same as they were for Dos, just with different namespaces. We'll also need to specify a
dependency on F#'s core library, so we'll add the following to paket.dependencies
:
1:
|
|
...and copy paket.references
over from Dos. Once it's in our project, add an FSharp.Core
line to it.
This is a good time to talke about F# project structure. F# projects are historically not split into directories, as
compilation order is significant, and having them in the same directory allows the tooling to ensure that the
compilation order is preserved. With the structure of the .fsproj
file, this is not necessarily a limitation (as you
can edit the .fsproj
file directly to specify subdirectory compilation order), but we'll follow it for our purposes
here.
The module is created as HomeModule.fs
in the project root:
1: 2: 3: 4: 5: 6: 7: 8: 9: |
|
If you look at Dos, you can see how the translation occurred:
- "using" becomes "open", and moves inside the namespace delaration
-
F# does not express constructors in the way C# folks are used to seeing them. Parameters to the class are specified
in the type declaration (or a
new
function, which we don't need for our purposes), and then are visible throughout the class. -
Since we don't have an explicit constructor where we can wire up the
Get()
method call, we accomplish it using ado
binding; this is code that will be run every time the class is instantiated. Theas this
at the end oftype HomeModule()
allows us to usethis
to refer to the current instance; otherwise,do
cannot see it. -
This also illustrates the syntax differences in defining lambdas between C# and F#. F# uses the
fun
keyword to indicate an anonymous function. The_
is used to indicate that we do not care what the parameter is; since this request doesn't require anything from theDynamicDictionary
Nancy provides, we don't.
We rename Program.fs
to App.fs
, and in this file, we provide the contents from both Startup.cs
and App.cs
.
1: 2: 3: 4: 5: 6: 7: 8: 9: 10: 11: 12: 13: 14: 15: 16: |
|
The Startup
class is exactly the same as the C# version, though it appears much differently. The UseNancy()
method
returns quite a complex result, but the parameter to the UseOwin()
method expects an Action<>
; by definition, this
returns void
*. In F#, there is no implicit throwaway of results**; you must explicitly mark results that should be
ignored. UseNancy
also expects an Action<>
, so we end up with an extra lambda and two ignore
s to accomplish the
same thing.
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
look like). An F# source file must start with either a namespace or module declaration; also, any code (let
, do
,
member
, etc.) cannot be simply in a namespace. We start with the Tres
namespace so that our Startup
class's full
name will be Tres.Startup
, so we have to define a module for our let
binding / entry point.
At this point, dotnet build
will fail. I mentioned compilation order earlier; we've added one file and renamed the
other, but we have yet to tell the compiler about them, or how they should be ordered. Back in Tres.fsproj
, modify the
ItemGroup
that has a <Compile Include="Program.fs" />
, to look like the following:
1: 2: 3: 4: |
|
(In the future, we'll add updating this list to our discipline of creating a new file.)
Now, we can execute dotnet run
, watch it start, visit localhost:5000, and see our F# message.
* The unit
type in F# is the parallel to this, but there's more to it than just "something else to call void
."
** For example, StringBuilder.Append()
returns the builder so you can chain calls, but it also mutates the builder,
and you don't have to provide a variable assignment for every call. In F#, you would either need to provide that, or
pipe the output (|>
) to ignore
.
namespace FSharp
--------------------
namespace Microsoft.FSharp
type HomeModule =
inherit NancyModule
new : unit -> HomeModule
--------------------
new : unit -> HomeModule
type NancyModule =
member After : AfterPipeline with get, set
member Before : BeforePipeline with get, set
member Context : NancyContext with get, set
member Delete : path:string * action:Func<obj, obj> * ?condition:Func<NancyContext, bool> * ?name:string -> unit + 5 overloads
member Get : path:string * action:Func<obj, obj> * ?condition:Func<NancyContext, bool> * ?name:string -> unit + 5 overloads
member Head : path:string * action:Func<obj, obj> * ?condition:Func<NancyContext, bool> * ?name:string -> unit + 5 overloads
member ModelBinderLocator : IModelBinderLocator with get, set
member ModelValidationResult : ModelValidationResult with get, set
member ModulePath : string with get, set
member Negotiate : Negotiator
...
--------------------
NancyModule() : NancyModule
NancyModule(modulePath: string) : NancyModule
NancyModule.Get(path: string, action: System.Func<obj,System.Threading.CancellationToken,System.Threading.Tasks.Task<obj>>,?condition: System.Func<NancyContext,bool>,?name: string) : unit
NancyModule.Get<'T>(path: string, action: System.Func<obj,System.Threading.Tasks.Task<'T>>,?condition: System.Func<NancyContext,bool>,?name: string) : unit
NancyModule.Get(path: string, action: System.Func<obj,System.Threading.Tasks.Task<obj>>,?condition: System.Func<NancyContext,bool>,?name: string) : unit
NancyModule.Get<'T>(path: string, action: System.Func<obj,'T>,?condition: System.Func<NancyContext,bool>,?name: string) : unit
NancyModule.Get(path: string, action: System.Func<obj,obj>,?condition: System.Func<NancyContext,bool>,?name: string) : unit
type Startup =
new : unit -> Startup
member Configure : app:IApplicationBuilder -> unit
--------------------
new : unit -> Startup
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
(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>>>
from Tres
type EntryPointAttribute =
inherit Attribute
new : unit -> EntryPointAttribute
--------------------
new : unit -> EntryPointAttribute
type WebHostBuilder =
new : unit -> WebHostBuilder
member Build : unit -> IWebHost
member ConfigureAppConfiguration : configureDelegate:Action<WebHostBuilderContext, IConfigurationBuilder> -> IWebHostBuilder
member ConfigureServices : configureServices:Action<IServiceCollection> -> IWebHostBuilder + 1 overload
member GetSetting : key:string -> string
member UseSetting : key:string * value:string -> IWebHostBuilder
--------------------
WebHostBuilder() : WebHostBuilder