なぜ、F#で作るのかはさておき、C#のWeb APIアプリをF#にコンバートしてみます。本当のところは、ASP.NET Core MVCのほうをF#に対応させてみたかったのですが、Razor構文がF#に(たぶん)対応していないので無理ってことで、ViewページのないWeb APIで試しています。まあ、F#の関数型なところがステートレスなHTTPプロトコルに合っているんじゃないか、常々思ってはいるのですが。
いわゆる、ASP.NET Core Web Application(.NET Core) をF#に直します。
単純なWebアプリのほうは、いくつかサンプルが見つかるのですが、Web APIのコンバートは見つからなかったので参考になると思います。先行きは、SQLiteを使って、データベースのアクセスまで作りたいところ。
Visual Studio Codeの準備
実は、F#の.NET Coreは、Visual Studio 2015は対応していません。コードのコンバートなのでちまちまとテキストエディタを使ってもよいのですが、さすがにインテリセンスがないとF#のコードは組みづらい。
Visual Studio Code
https://www.visualstudio.com/ja-jp/products/code-vs.aspx
を使うと、.NET CoreのF#でもインテリセンスが使えるようになります。ちなみに、LinuxとMacでもF#の.NET Core環境が使えます。
Visual Studio Codeをダウンロード&インストールして「表示」→「Extentions(拡張)」から、「Ionide-fsharp」をインストールします。
Windows版の場合は、Ionide-fsharpだけでよいのですが、LinuxとMacの場合は別途 mono もインストールします。拡張機能が mono を使っているからだと思うのだけど、そのうち .NET Coreに統一されるかもしれません。
インストール後に「有効」にして、VSCode を再起動すると、F#のコードでインテリセンスが使えるようになります。
C#の.NET CoreのWeb APIプロジェクトからコピー
Visual Studio 2015で.NET CoreのWeb APIプロジェクトを作って、C#のコード(Startup.cs、Program.cs、ValuesController.cs)以外のファイルをごっそりコピーします。
project.jsonの修正
コンソール版のF#コードを「dotnet new –lang F#」で作成して、2つのproject.jsonをうまくマージします。
F#の場合は、ビルドするときに前方参照が必要なので、”buildOptions” – “compile” – “includeFiles” にビルドする順番ファイル名を書いておきます。これが非常に面倒なんですが…先行きはどうなんでしょう?
{
"version": "1.0.0-*",
"buildOptions": {
"emitEntryPoint": true,
"preserveCompilationContext": true,
"debugType": "portable",
"compilerName": "fsc",
"compile": {
"includeFiles": [
"Controllers/ValuesController.fs",
"Startup.fs",
"Program.fs"
]
}
},
"tools": {
"Microsoft.AspNetCore.Server.IISIntegration.Tools": "1.0.0-preview2-final",
"dotnet-compile-fsc":"1.0.0-preview2-*"
},
"frameworks": {
"netcoreapp1.0": {
"imports": [
"dotnet5.6",
"portable-net45+win8"
],
"dependencies": {
"Microsoft.NETCore.App": {
"type": "platform",
"version": "1.0.0"
},
"Microsoft.FSharp.Core.netcore": "1.0.0-alpha-160629"
}
}
},
"dependencies": {
"Microsoft.NETCore.App": {
"version": "1.0.0",
"type": "platform"
},
"Microsoft.AspNetCore.Mvc": "1.0.0",
"Microsoft.AspNetCore.Server.IISIntegration": "1.0.0",
"Microsoft.AspNetCore.Server.Kestrel": "1.0.0",
"Microsoft.Extensions.Configuration.EnvironmentVariables": "1.0.0",
"Microsoft.Extensions.Configuration.FileExtensions": "1.0.0",
"Microsoft.Extensions.Configuration.Json": "1.0.0",
"Microsoft.Extensions.Logging": "1.0.0",
"Microsoft.Extensions.Logging.Console": "1.0.0",
"Microsoft.Extensions.Logging.Debug": "1.0.0",
"Microsoft.Extensions.Options.ConfigurationExtensions": "1.0.0"
},
"runtimeOptions": {
"configProperties": {
"System.GC.Server": true
}
},
"publishOptions": {
"include": [
"wwwroot",
"Views",
"Areas/**/Views",
"appsettings.json",
"web.config"
]
},
"scripts": {
"postpublish": [ "dotnet publish-iis --publish-folder %publish:OutputPath% --framework %publish:FullTargetFramework%" ]
}
}
コードをF#に作り変える
3つのC#のファイルをF#にコンバートします。
大体、1対1でコンバートができるのですが、F#のほうが型チェックが厳しいので、戻り値が void になる関数はまめに ignore していきます。戻り値が推論されて、実行時にメソッドが見つからなくてエラーになるのがハマりどころです。
Startup.cs
namespace ApiFSharp
open System
open System.Collections.Generic
open System.Linq
open System.Threading.Tasks
open Microsoft.AspNetCore.Builder
open Microsoft.AspNetCore.Hosting
open Microsoft.Extensions.Configuration
open Microsoft.Extensions.DependencyInjection
open Microsoft.Extensions.Logging
type Startup(env:IHostingEnvironment) =
let mutable _Configuration:IConfigurationRoot = null
do
let builder = new ConfigurationBuilder()
builder.SetBasePath(env.ContentRootPath)
.AddJsonFile("appsettings.json", true, true)
.AddJsonFile("appsettings." + env.EnvironmentName + ".json", true)
.AddEnvironmentVariables() |> ignore
_Configuration <- builder.Build()
member x.Cnfiguration
with get() = _Configuration
and set( value ) = _Configuration <- value
member x.ConfigureServices( services:IServiceCollection ) =
services.AddMvc() |> ignore
()
member x.Configure( app:IApplicationBuilder, env:IHostingEnvironment, loggerFactory:ILoggerFactory ) =
loggerFactory.AddConsole( _Configuration.GetSection("Logging")) |> ignore
loggerFactory.AddDebug() |> ignore
app.UseMvc() |> ignore
()
Web APIをどのPCからも受け付けられるように UseUrlsメソッドで「”http://*:5000″」のように追加しておきます。こうすると、Linux仮想マシンでWeb APIを動かして、PCのブラウザでチェックする、ということができます。
Program.fs
open System
open System.Collections.Generic
open System.IO
open System.Linq
open System.Threading.Tasks
open Microsoft.AspNetCore.Hosting
open Microsoft.AspNetCore.Builder
open ApiFSharp
[<EntryPoint>]
let Main argv =
let host = new WebHostBuilder()
let ihost =
host.UseKestrel()
.UseContentRoot(Directory.GetCurrentDirectory())
.UseIISIntegration()
.UseStartup<Startup>()
.UseUrls("http://*:5000")
.Build()
ihost.Run()
0
Startup.cs と Program.fs は定番の処理なので、Web APIのプロジェクトテンプレートを作ってしまえばよいと思います。
実際に手を入れていくのは、ValuesControllerクラスです。
ValuesController.cs
namespace ApiFSharp.Controllers
open System
open System.Collections.Generic
open System.Linq
open System.Threading.Tasks
open Microsoft.AspNetCore.Mvc
[<Route("api/[controller]")>]
type ValuesController() =
inherit Controller()
[<HttpGet>]
member x.Get () =
["value1"; "value2" ]
[<HttpGet("{id}")>]
member x.Get (id:int) =
"value"
[<HttpPost>]
member x.Post([<FromBody>] value:string) =
()
[<HttpPut("{id}")>]
member x.Put( id:int, [<FromBody>] value:string) =
()
[<HttpDelete("{id}")>]
member x.Delete( id:int ) =
()
データベースにアクセスしていないので、固定文字列しか返していないけど、SQLite を使ってデータアクセスを作れば結構使えるようになるかなと。
ビルドして実行する
リストアして、ビルドして、実行します。
dotnet restore dotnet build dotnet run
VSCodeで、Shift+Ctrl+B を押すとビルドができます。
実行する
PowerShell で dotnet run してブラウザで http://localhost:5000/api/values にアクセスします。
ちなみに Ubuntu 上の VSCode を使って F# プログラミングはこんな感じ。
固定の文字列を返すだけとか、計算するだけだとふつうに HttpListener を使って返すだけでも十分だろうという気もしますが、データベースアクセス付け加えたり、認証とかクッキーとかを使ってアクセス制限を考えたり、Controllerクラスのテストなどを考えると、ASP.NET Core を利用した Web API を F# で作るのもアリかな…という無理矢理な発想で :)
サンプルコード
サンプルコードはこちら
https://1drv.ms/u/s!AmXmBbuizQkXgfwK1qbhxJOHVP0bxQ






