経緯
DBとかにためてたデータをクエリしてcsvとしてダウンロードできるようなweb apiを作りたかった。
手順
.NET Coreのインストール
現在(2017/07/19)の最新版は.NET Core 2.0 Preview 2なのでここからダウンロードする。
インストーラを起動してぽちぽちすればインストール完了。
インストール完了したらPowerShellでdotnet --version
しましょう。
PS C:\Users\ghken> dotnet --version
2.0.0-preview2-006497
ちゃんと2.0のPreview 2が入ってるのが確認できます。
プロジェクトの生成
dotnet coreでは作成するアプリケーションの種類ごとにテンプレートが用意されているので確認してみましょう。
PS C:\Users\ghken> dotnet new
Template Instantiation Commands for .NET Core CLI
Usage: new [options]
Options:
-h, --help Displays help for this command.
-l, --list Lists templates containing the specified name. If no name is specified, lists all templates.
-n, --name The name for the output being created. If no name is specified, the name of the current directory
is used.
-o, --output Location to place the generated output.
-i, --install Installs a source or a template pack.
-u, --uninstall Uninstalls a source or a template pack.
--type Filters templates based on available types. Predefined values are "project", "item" or "other".
--force Forces content to be generated even if it would change existing files.
-lang, --language Specifies the language of the template to create.
Templates Short Name Language Tags
--------------------------------------------------------------------------------------------------------
Console Application console [C#], F#, VB Common/Console
Class library classlib [C#], F#, VB Common/Library
Unit Test Project mstest [C#], F#, VB Test/MSTest
xUnit Test Project xunit [C#], F#, VB Test/xUnit
ASP.NET Core Empty web [C#] Web/Empty
ASP.NET Core Web App (Model-View-Controller) mvc [C#], F# Web/MVC
ASP.NET Core Web App (Razor Pages) razor [C#] Web/MVC/Razor Pages
ASP.NET Core with Angular angular [C#] Web/MVC/SPA
ASP.NET Core with React.js react [C#] Web/MVC/SPA
ASP.NET Core with React.js and Redux reactredux [C#] Web/MVC/SPA
ASP.NET Core Web API webapi [C#] Web/WebAPI
Nuget Config nugetconfig Config
Web Config webconfig Config
Solution File sln Solution
Razor Page page Web/ASP.NET
MVC ViewImports viewimports Web/ASP.NET
MVC ViewStart viewstart Web/ASP.NET
Examples:
dotnet new mvc --auth Individual
dotnet new razor
dotnet new --help
今回はwebapiを使ってみます。
webapiのプロジェクトを生成するにはdotnet new webapi
を実行すればいいのですが、compoer create-project
やrails new
と違ってプロジェクトのフォルダは生成されないのであらかじめプロジェクトのフォルダに移動したうえで実行しましょう。
PS C:\Users\ghken> mkdir csvapi
ディレクトリ: C:\Users\ghken
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 2017/07/19 17:27 csvapi
PS C:\Users\ghken> cd csvapi
PS C:\Users\ghken\csvapi> dotnet new webapi
The template "ASP.NET Core Web API" was created successfully.
This template contains technologies from parties other than Microsoft, see https://aka.ms/template-3pn for details.
Processing post-creation actions...
Running 'dotnet restore' on C:\Users\ghken\csvapi\csvapi.csproj...
Restoring packages for C:\Users\ghken\csvapi\csvapi.csproj...
Restore completed in 26.11 ms for C:\Users\ghken\csvapi\csvapi.csproj.
Generating MSBuild file C:\Users\ghken\csvapi\obj\csvapi.csproj.nuget.g.props.
Generating MSBuild file C:\Users\ghken\csvapi\obj\csvapi.csproj.nuget.g.targets.
Restore completed in 1.75 sec for C:\Users\ghken\csvapi\csvapi.csproj.
Restore succeeded.
これでプロジェクトが生成されました。
生成されたものを確認しておきましょう。
PS C:\Users\ghken\csvapi> ls
ディレクトリ: C:\Users\ghken\csvapi
Mode LastWriteTime Length Name
---- ------------- ------ ----
d----- 2017/07/19 17:27 Controllers
d----- 2017/07/19 17:27 obj
d----- 2017/07/19 17:27 wwwroot
-a---- 2017/07/19 17:27 178 appsettings.Development.json
-a---- 2017/07/19 17:27 228 appsettings.json
-a---- 2017/07/19 17:27 582 csvapi.csproj
-a---- 2017/07/19 17:27 623 Program.cs
-a---- 2017/07/19 17:27 874 Startup.cs
動かしてみる
dotnet coreのアプリケーションを実行するにはdotnet run
コマンドを使います。
PS C:\Users\ghken\csvapi> dotnet run
Hosting environment: Production
Content root path: C:\Users\ghken\csvapi
Now listening on: http://localhost:5000
Application started. Press Ctrl+C to shut down.
どうやらローカルの5000番ポートで何やら動き始めたようです。
linuxとかならcurl使うんですがwindowsに入れてないのでInvoke-WebRequest
を使って動作確認してみましょう。
PS C:\Users\ghken> Invoke-WebRequest http://localhost:5000
Invoke-WebRequest : リモート サーバーがエラーを返しました: (404) 見つかりません
発生場所 行:1 文字:1
+ Invoke-WebRequest http://localhost:5000
+ ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+ CategoryInfo : InvalidOperation: (System.Net.HttpWebRequest:HttpWebRequest) [Invoke-WebRequest]、WebException
+ FullyQualifiedErrorId : WebCmdletWebResponseException,Microsoft.PowerShell.Commands.InvokeWebRequestCommand
ルートにアクセスすると404が返ってきます。
何をしてるのか把握するためにコードを読んでみましょう。
Controllersってフォルダがあるのでその中を見てみるとValuesController.cs
というファイルがあります。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
namespace csvapi.Controllers
{
[Route("api/[controller]")]
public class ValuesController : Controller
{
// GET api/values
[HttpGet]
public IEnumerable<string> Get()
{
return new string[] { "value1", "value2" };
}
// GET api/values/5
[HttpGet("{id}")]
public string Get(int id)
{
return "value";
}
// POST api/values
[HttpPost]
public void Post([FromBody]string value)
{
}
// PUT api/values/5
[HttpPut("{id}")]
public void Put(int id, [FromBody]string value)
{
}
// DELETE api/values/5
[HttpDelete("{id}")]
public void Delete(int id)
{
}
}
}
[Route("api/[controller]")]
って書いてあるので/api/values
にアクセスしたら何か起こりそうな気がします。
PS C:\Users\ghken> Invoke-WebRequest http://localhost:5000/api/values
StatusCode : 200
StatusDescription : OK
Content : ["value1","value2"]
RawContent : HTTP/1.1 200 OK
Transfer-Encoding: chunked
Content-Type: application/json; charset=utf-8
Date: Wed, 19 Jul 2017 08:43:11 GMT
Server: Kestrel
["value1","value2"]
Forms : {}
Headers : {[Transfer-Encoding, chunked], [Content-Type, application/json; charset=utf-8], [Date, Wed, 19 Jul 2017 08:43:11 GMT], [Server, Kestrel]}
Images : {}
InputFields : {}
Links : {}
ParsedHtml : mshtml.HTMLDocumentClass
RawContentLength : 19
contentとして["value1", "value2"]
が返ってきているのでどうやらGet()
が実行されたようです。
CSVを返す
Controllerのメソッドごとに返値の型がバラバラなのでこれをCSVっぽい型にすればいいんだろうか。
Controllerのメソッドの返値はIActionResultのオブジェクトで、ドキュメントを読んだところ、任意の型を返しておけばActionResultでラップしてくれるらしい。
配列とかを返せばそのままjsonでレスポンスしてくれる。
ではCSVはどうするか。
ContentResultってのがあるのでそれを使ってValuesControllerのGetを書き換えてみる。
ヘルパー関数としてContentが用意されていて、第一引数にコンテンツの文字列、第二引数にContent-Typeを入れてやればいいらしい。
public ContentResult Get()
{
return Content("hoge, fuga", "text/csv");
}
アクセスしてみる。
PS C:\Users\ghken> Invoke-WebRequest http://localhost:5000/api/values
StatusCode : 200
StatusDescription : OK
Content : hoge, fuga
RawContent : HTTP/1.1 200 OK
Content-Length: 10
Content-Type: text/csv
Date: Wed, 19 Jul 2017 08:56:33 GMT
Server: Kestrel
hoge, fuga
Forms : {}
Headers : {[Content-Length, 10], [Content-Type, text/csv], [Date, Wed, 19 Jul 2017 08:56:33 GMT], [Server, Kestrel]}
Images : {}
InputFields : {}
Links : {}
ParsedHtml : mshtml.HTMLDocumentClass
RawContentLength : 10
無事text/csv
で取得できました。
パスを変えてみる
api/values
のままでは使いづらいのでControllerを追加して別のpathでリクエストを受けるようにしてみましょう。
今回は/api
でリクエストを受けるようなControllerを追加したいと思います。
Controller/RootController.cs
を作成してそれっぽく実装してみます。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
namespace csvapi.Controllers
{
[Route("/api")]
public class RootController : Controller
{
[HttpGet]
public ContentResult Get() => Content("hoge, fuga", "text/csv");
}
}
これで行けるか。
PS C:\Users\ghken> Invoke-WebRequest http://localhost:5000/api
StatusCode : 200
StatusDescription : OK
Content : hoge, fuga
RawContent : HTTP/1.1 200 OK
Content-Length: 10
Content-Type: text/csv
Date: Wed, 19 Jul 2017 09:10:31 GMT
Server: Kestrel
hoge, fuga
Forms : {}
Headers : {[Content-Length, 10], [Content-Type, text/csv], [Date, Wed, 19 Jul 2017 09:10:31 GMT], [Server, Kestrel]}
Images : {}
InputFields : {}
Links : {}
ParsedHtml : mshtml.HTMLDocumentClass
RawContentLength : 10
成功です。
クエリストリングを扱う
条件をを受けとってそれによって加工したデータを返したいので、クエリストリングを扱う方法を調べます。
ControllerクラスのRequestにQueryプロパティが存在しているのでそれが使えそう。
keyにhogeがあればそれを、なければhoge
を使って出力するようにしてみた。
public ContentResult Get()
{
var hoge = Request.Query.ContainsKey("hoge") ? Request.Query["hoge"].First() : "hoge";
return Content(hoge + ",fuga", "text/csv");
}
実行。
PS C:\Users\ghken> Invoke-WebRequest http://localhost:5000/api?hoge=fuga
StatusCode : 200
StatusDescription : OK
Content : fuga,fuga
RawContent : HTTP/1.1 200 OK
Content-Length: 9
Content-Type: text/csv
Date: Wed, 19 Jul 2017 09:24:35 GMT
Server: Kestrel
fuga,fuga
Forms : {}
Headers : {[Content-Length, 9], [Content-Type, text/csv], [Date, Wed, 19 Jul 2017 09:24:35 GMT], [Server, Kestrel]}
Images : {}
InputFields : {}
Links : {}
ParsedHtml : mshtml.HTMLDocumentClass
RawContentLength : 9
PS C:\Users\ghken> Invoke-WebRequest http://localhost:5000/api
StatusCode : 200
StatusDescription : OK
Content : hoge,fuga
RawContent : HTTP/1.1 200 OK
Content-Length: 9
Content-Type: text/csv
Date: Wed, 19 Jul 2017 09:24:40 GMT
Server: Kestrel
hoge,fuga
Forms : {}
Headers : {[Content-Length, 9], [Content-Type, text/csv], [Date, Wed, 19 Jul 2017 09:24:40 GMT], [Server, Kestrel]}
Images : {}
InputFields : {}
Links : {}
ParsedHtml : mshtml.HTMLDocumentClass
RawContentLength : 9
うまくいきました!