伝説のどいつまの伝説~失敗編~

岩美に永住したい新米エンジニアのあれやこれやな話。

【C#】ASP.NET Core MVCを使ってみよう。-After①-

前回の内容はこちらから。 d01tsumath.hatenablog.com

今回はAfterということで、レビューで修正していただいたところを復習兼ねて投下していきます。
長いので、Before->Afterだけみたいよーって方は以下GitHubをご覧ください。

github.com

課題:ASP.NET Core MVCを使って前回の課題を置き換えてみよう

まずは、HomeController.csファイルから修正していきます。

Before

public IActionResult Index()
{
    string url = @"http://d01tsumath.hatenablog.com/rss";

    try
    {
        // RSS読み込み
        XElement element = XElement.Load(url);

        // channelの取得
        XElement channelElement = element.Element("channel");

        //itemの取得
        IEnumerable<XElement> elementItems = channelElement.Elements("item");

        var titleList = new List<Title>(5);

        for (int i = 0; i < 5; i++)
        {
            XElement item = elementItems.ElementAt(i);

            // 先頭の<link></link>を取り出す
            var linkText = Regex.Replace(item.Element("link").ToString(), "<[^>]*?>", "");
            var title = new Title
            {
                Name = item.Element("title").Value,
                URL = linkText
            };
            titleList.Add(title);
        }

        ViewData["titles"] = titleList;
    }
    catch (Exception e)
    {
        Console.WriteLine(e.Message);
    }

    return View();
}


After

まず、RSSフィードの読み込みです。

const string rssFeed = "http://d01tsumath.hatenablog.com/rss";

var element = XElement.Load(rssFeed);

var articles
    = element
    .Descendants("item")
    .Select(x =>
    {
        var title = x.Element("title").Value;
        var url = x.Element("link").Value;
        return new Article(title, url);
    })
    .Take(5)
    .ToArray();


const string rssFeed = "http://d01tsumath.hatenablog.com/rss";
まず、先頭のurlは、単純にurlだと何のURLなのかわからないので、意図がわかる変数名にします。
rssFeedの値は以後変更されないので定数としてconstを使うのが無難ですね。


var articles = element.Descendants("item")
Beforeでは、itemまで2段階経ていますが、ここまでスッキリしました。
Elementは子ノードを取得するのに対し、Descendantsは子孫ノードを取得します。(以下ページ参照)

docs.microsoft.com


.Select(x =>
    {
        var title = x.Element("title").Value;
        var url = x.Element("link").Value;
        return new Article(title, url);
    })

Descendants()の戻り値はIEnumerable<T>です。
IEnumerable<T>ということはLinqが使えます。積極的に使っていきたい…!(切実)
Selectでデータを成型します。
new Article()Articleクラスは後ほど作るとして、今必要なのはitemの中にある 記事タイトル記事のURL だけなのでitemからtitleurlのみ取得します。


.Take(5).ToArray();
課題としてはRSSフィードから5件だけ取得したいので、Take()で先頭から5つ取ってきて配列にします。
上から〇つなどの条件がある場合は、For文で回して取得するよりTake()を使えば良い!っていう思考に切り替えていきたいものです。

ここから、非同期処理をプラスしていきます。

ネットワーク通信や読み込み処理などは非同期処理を使うべし!

前職では非同期処理を使わなければならない場面って遭遇しなかったので、C#を書いてても「非同期処理って何w」という状態だったのが正直なところ。
現職に入社して間もなく、有難いことに非同期処理を教えていただいたわけですが、今回の課題にて非同期処理をどう活用すればよいのか学びました。

非同期処理の採用を検討する必要があるシーンには、例えば次のようなものが挙げられる。
・重たい処理をするとき
・ファイルの読み書きやネットワーク通信など、不定な待ち時間が発生する処理をするとき
引用:-第1回 .NET開発における非同期処理の基礎と歴史-

レビューのときにも、詳しく説明してもらったんですが、非同期処理を使うことによって
パフォーマンス(リソース消費)が良くなる
->より複数のリクエスト処理もできるようになる
->結果、アプリケーション全体に無駄がなくなる
ということ(らしい)です。
1つのリクエストをどう処理するかだけでなく、全体のパフォーマンスを上げることも考えていく必要があるんですね~。


では、非同期処理を使うとどう変わっていくのでしょうか…。

public async ValueTask<IActionResult> Index()
{
    const string rssEndpoint = "http://d01tsumath.hatenablog.com/rss";

    var client = new HttpClient();
    var response = await client.GetAsync(rssEndpoint);
    var rssFeed = await response.Content.ReadAsStringAsync();
    
    var articles
        = XElement.Parse(rssFeed)
        .Descendants("item")
        .Select(x =>
        {
            var title = x.Element("title").Value;
            var url = x.Element("link").Value;
            return new Article(title, url);
        })
        .Take(5)
        .ToArray();

    return this.View();
}


public async ValueTask<IActionResult> Index()
つい先日Task / Task<T>を使い始めたばかりなのに、ValueTask<T>出てきた~って思いました(笑)。
Task / Task<T>だと戻り値の型に制約がありますが、ValueTask<T>はTask型以外の戻り値もOK!
更に、Task / Task<T>は参照型(ヒープ領域を使う!)なのに対して、ValueTask<T>は値型(スタック領域を使う!)っていう違いもあります。
(ついでに変数名も変わってます…!)
2019.08.23 追記:Taskでも戻り値に制約はないようです…Oh...しかもValueTaskはヒープ領域を使うこともあるようなのでおいおい詳しく学んでいきたいと思います!


const string rssEndpoint = "http://d01tsumath.hatenablog.com/rss";

var client = new HttpClient();
var response = await client.GetAsync(rssEndpoint);
var rssFeed = await response.Content.ReadAsStringAsync();

XElement.Load()はURL指定してRSSフィードを読み込んでくれるものですが、処理が終わるまで待ち時間が発生してしまいます。
.Load()の代わりに非同期処理を使ってあげることで、CPUの処理能力を持て余すことがなくなり、通信中他の処理を裁くこともできます。
ちなみに調べてみたところHttpClientに用意されているメソッドは、全て非同期版だそうです。


次回は、ModelとViewの修正をしていきたいと思います!