vaguely

和歌山に戻りました。ふらふらと色々なものに手を出す毎日。

ASP.NET Coreに触れてみる 2

はじめに

次はモデルの話に行く予定でしたが、読んでる本MVC のコントローラーのルーティングの話に行ったのでその話を。

依存の追加について

それはそれとして、前回 Dependency Injection の話で、サービスに Inject する方法を以下のように書きました。

~省略~
public class Startup {
    public void ConfigureServices(IServiceCollection services) {

        services.AddSingleton< IDiSample, DiSample>();

        services.AddMvc();
    }
    ~省略~
  }
~省略~

これは下記のようにも書くことができるようです。

~省略~
public class Startup {
    public void ConfigureServices(IServiceCollection services) {

        これも OK
        services.AddSingleton< IDiSample>(new DiSample());

        services.AddMvc();
    }
    ~省略~
  }
~省略~

ルーティングについて

ASP.NET Core でのルーティングには、規約ベースと属性ベースが存在します。

規約ベースのルーティング

規約ベースのルーティングは、 Startup の Configure で MVC を有効にする( UseMvc() を実行する)時にコントローラーなどへのマッピングを名前指定で行います。

Startup.cs

using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Routing;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;

namespace WebApplication1 {
    public class Startup {
        ~省略~
        public void Configure(IApplicationBuilder app, IHostingEnvironment env) {
            ~省略~
            
            app.UseMvc(routes => {
                routes.MapRoute(
                    name: "default",
                    // controllerとして指定している値+ Controllerという名前のクラスの,
                    // actionで指定している名前のメソッドが実行される.
                    template: "{controller=Home}/{action=Index}/{id?}");
            });
            
        }
    }
}

これで localhost:5XXX や localhost:5XXX/Home 、 localhost:5XXX/Home/Index にアクセスしたとき Controllers フォルダに作成した HomeController.cs の Index() が実行されるようになります。

HomeController.cs

using Microsoft.AspNetCore.Mvc;

namespace WebApplication1.Controllers {
    public class HomeController : Controller {
        public IActionResult Index() {
            // Views/Home に作成した Index.cstml を表示する.
            return View();
        }        
    }
}

なお上記 Startup でのマッピングは、代わりに app.UseMvcWithDefaultRoute() を実行しても同じ動作となります。 (controller や action 名を変更する場合は明示的に指定する必要があります)

前回登場した Razor View では、(自作の)コントローラーを使用していなかったので app.UseMvc() (引数なし)を実行していました。 なおこれは app.UseMvc(routes => { }); と同じ動作をします。

また前回の Razor View は Pages フォルダにあったのに対し、今回使用するビューは Views/Home にあり、そのまま移動させてしまうとエラーとなるため、再度作り直したほうが良さそうです。

上記ではコントローラー名、アクション名を URL と合致させていますが、別々のものを指定することもできます。

Startup.cs

app.UseMvc(routes => {
        routes.MapRoute(
            name: "default",
            template: "{id?}",
            defaults: new { controller = "Home", action = "Index" });
    });

これで localhost:5XXX または localhost:5XXX/0 といった URL でのみ指定のページが表示されるようになります。

属性ベースのルーティング

Spring boot だと @Controller などのアノテーションをつけて指定する方法がこちらです。 (見た感じ、というだけで実際に同じ動作かと言われると自信がありませんが)

Startup.cs

~省略~
namespace WebApplication1 {
    public class Startup {
        ~省略~
        public void Configure(IApplicationBuilder app, IHostingEnvironment env) {
            ~省略~
            
            app.UseMvc();
            
        }
    }
}

HomeController.cs

using Microsoft.AspNetCore.Mvc;

namespace WebApplication1.Controllers {
    public class HomeController : Controller {
        [Route("")]
        [Route("Home")]
        [Route("Home/Index")]
        public IActionResult Index() {
            return View();
        }        
    }
}

これで localhost:5XXX や localhost:5XXX/Home 、 localhost:5XXX/Home/Index にアクセスしたとき Index() が実行されます。

コントローラーのクラス名を HomeController としていますが、マッピングする URL と合致はしていなくても OK です。

ただし、ビューのフォルダ名とは合致していないとエラーになるので、直接指定する必要があります。

HomeController.cs

public IActionResult Index() {
    return View("~/Views/Home/Index.cshtml");
}

もう少し使ってみないといけないとは思いますが、個人的には属性ベースの方が好みです。
マッピングする URL と呼ばれるメソッドやコントローラーがそばにあるあたりが。

キャッチオールルートパラメーター

例えば localhost:5XXX/Home/ 以下のすべての URL にアクセスされた場合に一括で処理をしたい場合。

HomeController.cs

using Microsoft.AspNetCore.Mvc;

namespace WebApplication1.Controllers {
    public class HomeController : Controller {
        [Route("")]
        [Route("Home")]
        [Route("Home/{*article}")]
        public IActionResult Index() {
            return View();
        }        
    }
}

{*article} を指定することで、例えば例えば localhost:5XXX/Home/Index であろうと localhost:5XXX/Home/Sample であろうとすべて Index() が呼ばれるようになります。

これをキャッチオールルートパラメーターというそうです。
安全保障貿易管理に出てくるキャッチオール規制を思い出す名前ですが。

HTTPリクエス

GET、POST などの HTTP リクエストの指定をすることもできます。

HomeController.cs

public class HomeController : Controller {
    [HttpGet]
    [Route("")]
    [Route("Home")]
    [Route("Home/Index")]
    public IActionResult Index() {
        return View();
    }        
}

これで該当の URL に GET リクエストが渡された場合のみ Index() が実行されるようになります。

※規約ベースのルーティングで、上記を実現する方法は見つけられませんでした。。
IRouteBuilder の MapGet などで HTTP リクエストを指定する方法はあるのですが、ビューをどう表示するかが見つけられずorz

また Web API を作りたい場合など、 URL マッピングをクラス全体に対してつけることも可能です。

以下は Visual Studio でコントローラークラスをジェネレートするとできるものです(名前は任意)。

MessageController.cs

using System.Collections.Generic;
using Microsoft.AspNetCore.Mvc;

namespace WebApplication1.Controllers {
    [Produces("application/json")]
    [Route("api/Message")]
    public class MessageController : Controller {
        // GET: api/Message
        [HttpGet]
        public IEnumerable Get() {
            return new string[] { "value1", "value2" };
        }

        // GET: api/Message/5
        [HttpGet("{id}", Name = "Get")] 
        public string Get(int id) {
            return "value";
        }
        
        // POST: api/Message
        [HttpPost]
        public void Post([FromBody]string value) {
        }
        
        // PUT: api/Message/5
        [HttpPut("{id}")]
        public void Put(int id, [FromBody]string value) {
        }
        
        // DELETE: api/ApiWithActions/5
        [HttpDelete("{id}")]
        public void Delete(int id) {
        }
    }
}

これで localhost:5XXX/api/Message に例えば Web ブラウザでアクセスすれば、"value1"、"value2" という値が JSON で表示されます。

エラーページ

ここまでで URL のマッピングや表示するビューについては少しわかった気がするのですが、このままだと 404 などのエラーページが表示されません。

これを解決するには、 Startup.cs の Configure で設定が必要です。

Startup.cs

~省略~
namespace WebApplication1 {
    public class Startup {
        ~省略~
        public void Configure(IApplicationBuilder app, IHostingEnvironment env) {
            ~省略~
            
            app.UseStatusCodePages();
            
        }
    }
}

これで例えば存在しない URL にアクセスすると、 Status Code: 404; Not Found のようなテキストが表示されるようになります。

ここで例えばビューとして用意したページを見せたい場合、下記のように指定することができます。

Startup.cs

~省略~
namespace WebApplication1 {
    public class Startup {
        ~省略~
        public void Configure(IApplicationBuilder app, IHostingEnvironment env) {
            ~省略~
            
            app.UseStatusCodePagesWithRedirects("/Error/{0}");
            
        }
    }
}

この {0} には例えば 404 エラーなら「404」という値が入るので、

HomeController.cs

~省略~
[Route("/Error/404")]
public IActionResult Error404() {
    return View("~/Views/Home/ErrorPageNotFound.cshtml");
}
~省略~

あとは(今回の場合) Views/Home に ErrorPageNotFound.cshtml を作れば完了です。

ErrorPageNotFound.cshtml

@{
    ViewData["Title"] = "ErrorPageNotFound";
}

< h2>Page Not Found< /h2>
< div>おめーに見せるページはねぇ!< /div>

おわりに

今回解決できなかった(自分の中でまとまらなかった)点として、コントローラーの分割があります。

途中で触れた通りコントローラーで実行されるメソッドを URL で指定するわけですが、例えばビューを表示する処理のみをコントローラー1で行い、CRUD 処理をコントローラー2で行う、といった分割をするべきかどうか。

まぁ作るものの規模にもよるでしょうし、もう少し実際に作ってみながらその辺りは確かめてみたいと思います。

次回こそモデルの話。。。のハズ。

参照