なぜ、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” にビルドする順番ファイル名を書いておきます。これが非常に面倒なんですが…先行きはどうなんでしょう?
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 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 | { "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
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 28 29 30 31 32 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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
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 | 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 を使ってデータアクセスを作れば結構使えるようになるかなと。
ビルドして実行する
リストアして、ビルドして、実行します。
1 2 3 | 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