今日もちょいつか

お酒の飲み過ぎか、それとも歳のせいなのか毎日ちょっぴり疲れ気味なフリーのソフト屋です。ソフト開発の話題をblogにしてみます。

macOS Mojaveへアップグレード。やはりApache環境動かず

macOS High Sierraから1年またmacOSのバージョンがアップされました。
macOS Mojave モハベと読むらしいですね。
今回はちょっと語感があんまりカッコよくないですねー。

で、
アップグレードすると決まってlocalのApache環境が動かなくなります。
これは、OSに付属しているApacheを利用しているから
アップグレードすると設定ファイルがリネームされてしまうからです。

備忘録的にどのファイルが書き換えられたかメモっておきます。

/etc/apache2/httpd.conf
/etc/apache2/extra/httpd-userdir.conf
/etc/apache2/extra/httpd-vhosts.conf

この3ファイルが「~previous」をつけられてリネームされておりました。
3ファイルを戻してOKとなりました。

今回のグレードアップで
Apache2.4.34
PHP 7.1.19
となりました。

ASP.NET Core MVC MySQLへ画像ファイルを保存・表示してみる

ASP.NETで画像ファイルをMySQLに保存する方法を調査してみました。
正常に保存されているか確認するために、表示もしてみます。
Visual Studio Community 2017 for Mac で動作確認しています。

Model

Idと画像ファイル保存用だけのシンプルなクラスです。

public class TestImage
{
    [Key]        
    public int Id { get; set; }

    [Column(TypeName = "mediumblob")]
    public byte[] ImageFile { get; set; }
}

C#側は、byte[]、MySQL側は mediumblob型 を指定します。
MySQLの型を指定しないでマイグレーションを実行すると
blob型にマッピングされます。
blob型だと、保存できるファイルサイズが64KBまでとなりますので注意が必要です。
あとは、マイグレーションを実行してMySQLにテーブルを作成します。

Controller MySQLへ保存

using System.Drawing;
using System.IO;

public IActionResult Index()
{
    Bitmap Image = new Bitmap("/tmp/20151101-1.png");
    MemoryStream ms = new MemoryStream();
    Image.Save(ms, Image.RawFormat);
    
    var TestImage = new TestImage
    {
        ImageFile = ms.ToArray()
    };

     DbContext.TestImage.Add(TestImage );
     DbContext.SaveChanges();

     return View();
}

画像を保存するテストなので/tmpフォルダに画像ファイルがある前提です。

画像を表示する View と Controller

まずはViewです。

<div>
    <p>画像表示</p>
    <img src="Home/ImageDisp/5" width="50">
</div>

そして、Controller。

public IActionResult ImageDisp(int Id)
{
         var Img = DbContext.TestImage.SingleOrDefault(m => m.Id == Id);
         MemoryStream ms = new MemoryStream(Img.ImageFile);

         return new FileStreamResult(ms, "image/png");
}

引数にIdを渡して、そのIdから画像のデータを取得して画像データを戻します。

今回は案外ハマらずにすんなりと実現することができました。
これぐらい順調に実現できるとASP.NETも楽しく感じます。

C# LINQで動的なWhereを実現する

LINQを使うととても便利で驚かされます。
複雑なLINQを書いている時には、パズルみたいで不思議な楽しみもあるものです。

このLINQ
決まった条件での抽出(Where)はいいのですが、条件が動的な場合に
とたんに途方にくれてしまいます。

たとえば、売上データを検索するとして
日付で検索したり、金額で検索したりとゆうことはよくあると思います。
つまり、検索条件があったりなかったり。

SQL文でデータベースを操作しているときには
SQL文の文字列を組み立てるだけですのでとても簡単なのですが。

で、
LINQで動的にWhereを実現する方法を調べてみました。
Visual Studio Community 2017 for Mac で動作確認しています。

LINQ拡張機能

LINQの記述方法がラムダ式の場合の方法です。
ラムダ式だとメソッドチェーンでだらだらと記述をすることができます。
この時に、自分で拡張機能を作成すると独自のメソッドを書くことができます。

ものすごーくネットを検索して見つけました。

using System.Linq;
using System.Linq.Expressions;

public static IQueryable<TSource> WhereIf<TSource>
                            (this IQueryable<TSource> Source, bool Condition, Expression<Func<TSource, bool>> Predicate)
    {
            if (Condition)
                return Source.Where(Predicate);
            else
                return Source;
    }

実際に利用する時には

 var Result = DbContext.Uriage
                       .WhereIf(viewSearch.Amount != null, u => u.Amount >= viewSearch.Amount)
                       .ToArray();

つまり、検索画面からの検索条件である「金額:Amount」の入力があった時だけ
Whereを実行してくれます。
これで動的なLINQを実現することができました。

ただ、
ネットで調べてやっと実現できただけで、自分の力ではとても実現できないですね。

Mac Smartyで画面が真っ白になる

PHP + Smartyで開発されたシステムをリプレースする仕事を受注したので
Macに環境をつくったのですが、画面が表示されません。

簡単に解決するだろうと思ったのですが、ハマりにハマり。
解決するまで相当な時間がかかってしまいました。

開発環境

・OS macOS High Sierra
・PHP7
Smarty 2.6.14

現象

ブラウザからhttpアクセスしても何も表示されずに
真っ白になる。
具体的には、compileフォルダへ出力されるキャッシュファイルの中身が
何もない。
tplファイルはきちんとあります。

解決した方法

smarty/Smarty_Compiler.class.phpの267行目くらいにある

$source_content = preg_replace($search.'e', "'"
                . $this->_quote_replace($this->left_delimiter) . 'php'
                . "' . str_repeat(\"\n\", substr_count('\\0', \"\n\")) .'"
                . $this->_quote_replace($this->right_delimiter)
                . "'"
                , $source_content);

$source_content = preg_replace_callback($search, create_function ('$matches', "return '"
                . $this->_quote_replace($this->left_delimiter) . 'php'
                . "' . str_repeat(\"\n\", substr_count('\$matches[1]', \"\n\")) .'"
                . $this->_quote_replace($this->right_delimiter)
                . "';")
                , $source_content);

へ変更したら無事に表示されるようになりました。

PHP7では、preg_replaceのe修飾子を使うと、Warningを吐きつつNULLを返すようですね。
それでtplファイルを読み込んで出力できなかったようです。
ネットを探し回ってやっと解決することができました。
ソフト開発の仕事はハマってばっかりですね。

ASP.NET Core MVC 複数ボタンのあるFormでSubmit先を変えたい

いわゆるCRUDではなくて、
一つのフォームで新規登録・修正・削除・一覧表示をしたいと思っています。
名称を登録するだけのような簡単なマスターメンテナンス画面でも
CRUDだとその分Viewが必要になるし、操作もちょっと面倒です。

CRUDだと一つの処理に一つのSubmitですので
Controller側のアクションも簡単なのですが
一つのフォームで全ての処理を行うには複数のSubmitボタンが必要となります。

その時にController側はどのように記述すれば複数のSubmitボタンに対応できるのか
調べてみました。
いつものことですが、今回もかなりハマってしまいました。
Visual Studio Community 2017 for Mac で動作確認しています。

どうしても動かなかった方法

ネットを調べてみると、ある程度同じような方法を発見することができました。
セレクター属性を作ってクリックされたボタンごとにアクションを実行させる方法です。
ボタンのvalue属性を利用して処理を分岐する方法もありましたが
新たにセレクター属性を作ったほうがMVC的な感じがしていいと思いました。

Viewには一つのFormに更新と削除ボタンが二つあります。

<input type="submit" name="btnUpdate" value="更新" class="btn btn-primary">
<input type="submit" name="btnDelete" value="削除" class="btn btn-danger">

属性の作成はこんな感じです。

public class ButtonAttribute : ActionMethodSelectorAttribute
  {
    public string ButtonName { get; set; }

    public override bool IsValidForRequest(ControllerContext context, System.Reflection.MethodInfo methodInfo)
    {
      return context.Controller.ValueProvider.GetValue(ButtonName) != null;
    }
  }

Controller側はこんな感じ。

 [HttpPost]
 [Button(ButtonName = "btnUpdate")]
 public IActionResult Update()
 {
       ViewData["Message"] = "Update.";
       return View("test");
 }

クリックされたボタンのname属性を記述して処理を振り分けています。
すごくわかりやすくて簡単に動いてくれるはずでした。

しかし!
この方法では自分の環境では動いてくれませんでした。
英語サイトも見まくって4,5日奮闘しましたが結局動きません。

最終的に動いた方法

どうしても一つのSubmitで一つのアクションに紐付けたくて
まだ見ていなかった英語サイトを一つ一つ見ていったら思いがけない解決法を発見しました。
解決策はこれです。

<input type="submit" formaction="Update" formmethod="post" value="更新" class="btn btn-primary">
<input type="submit" formaction="Delete" formmethod="post" value="削除" class="btn btn-danger">

Viewでのhtmlの記述を変更するだけで解決することができました。
HTML5になってからボタンに「formaction」と「formmethod」が記述できるようになったのですね。
恐ろしいほどの無知さに悲しくなるのでした。
ものすごく時間をかけて悩んだ挙句こんなオチでした。
でも、解決できてよかった!

ASP.NET Core MVC xUnitに挑戦(かなりの超初心者です)

どうやら、xUnit(テスティングフレームワーク)が
とても便利らしいとの情報をネットで発見したので調べてみました。
テストコードを記述することで、簡単に何度でもテストが
できるのでソースを変更した後の確認がとても楽なのでは!と思いました。

で、ネットを調べてみたのですが・・・。難しい。
ネットのみなさんの技術レベルが高すぎて初心者の私では
とても理解ができないのでした。
それでもなんとかxUnitを動かすことができたのでまとめておきます。

技術レベルがとても低いので、きちんとした作法にのっとっているのかさえ
わからずただ動いたー。だけです。
Visual Studio Community 2017 for Mac で動作確認しています。

テスト対象のプロジェクトを作成する

テスト対象用プロジェクトとして、プロジェクトを作成します。
f:id:Heinlein:20180206133436p:plain
そして「CalcClass.cs」を追加します。引数を加算して結果を返す単純なものです。

namespace xUnitProjct
{
    public class CalcClass
    {
        public int sum(int a, int b){
            int result = a + b;
            return result;
        }
    }
}

そして、HomeController.csはこんな感じ。

     public IActionResult Index()
     {
         CalcClass calc = new CalcClass();
         int subval = calc.sum(1, 2);
         ViewData["CalcValue"] = subval.ToString();
         return View();
     }

テスト用のプロジェクトを作成する

ソリューションを右クリック>追加>新しいプロジェクトを追加。
f:id:Heinlein:20180206101632p:plain
左側の.NET Core>テストを選択してxUnitテストプロジェクトを選択。
f:id:Heinlein:20180206102041p:plain
プロジェクト名は
先程のプロジェクト名 + 「.Tests」として、xUnitProjct.Tests で作成します。

テスト用プロジェクトに参照設定

テスト用に作成した「xUnitProjct.Tests」がテスト対象のプロジェクト「xUnitProjct」を認識できるように参照設定をします。
f:id:Heinlein:20180206103432p:plain
開いた画面の「プロジェクト」タブを選択すると
先程作成したxUnitProjctが現れるのでチェックをしてOKをクリックします。
f:id:Heinlein:20180206134616p:plain

テストコードを記述する

テスト用プロジェクトのUnitTest1.csにテスコトードを記述します。

namespace xUnitProjct.Tests
{
    public class UnitTest1
    {
        [Fact]
        public void Test1()
        {
            CalcClass calc = new CalcClass();
            int actual = calc.sum(1, 2);
            int expect = 3;
            Assert.Equal(expect, actual);
        }
    }
}

期待値と実際の結果が合致するかテストします。
実際のテストの実行は、メニュー > 表示 > テストを選択すると画面右上に
単体テストのタブが現れますので、「全て実行」をクリックするとテストが実行されます。
f:id:Heinlein:20180206143341p:plain
無事にテストが成功となりました。

一応確認のために、わざとテストを失敗させてみます。
期待値のところを expect = 3 から 5 に変更してテストを実行してみます。
f:id:Heinlein:20180206143756p:plain
赤く表示されてテストが失敗となりました。きちんとテストができているようです!

さいごに

なんとかxUnitを動かすことができました。
参考にしたサイトはこちらです。
docs.microsoft.com
参考にしたサイトでは、dotnetコマンドを使って実現していたので
なんとかVisual Studio Community 2017 for Macで実行できるようにがんばりました。

テストの内容としてはとても現実的ではないのでただxUnitが動いたに過ぎません。
ここからいかに現実的で効率の良いテストコードを作成できるかだと思います。

ASP.NET Core MVC CSRF対策を調べてみた

Webシステムの脆弱性CSRF (Cross-Site Request Forgery)
いわゆる、クロスサイトリクエストフォージェリがあります。
その対策として
フォーム側にトークンを埋め込んで、受ける側でトークンを検証することで
自サーバーからのPOSTであることが保証されます。
ASP.NET Core MVCではどのようにするのか調べてみました。
Visual Studio Community 2017 for Mac で動作確認しています。

View側の記述

タグヘルパーでFormを記述します。

<form asp-action="Index" method="post">

すると、Formの中にトークン情報が仕込まれます。
hidden属性で __RequestVerificationToken のvalue
トークン情報がセットされます。

Controller側の記述

POSTを受けるController側にも記述します。

   [HttpPost]
   [ValidateAntiForgeryToken]
   public IActionResult Index(ItemGrpViewModel model)

[ValidateAntiForgeryToken]を記述するだけです。
実際に効いているか確認してみます。
Chromeデベロッパーツールで__RequestVerificationToken のvalueの中身を変更して
Submitしてみます。

f:id:Heinlein:20180201141505p:plain
きちんとエラーではじかれました。

最後にもっと便利な方法がありました

[ValidateAntiForgeryToken]をいちいち全てのPOSTアクションへ記述するのは
面倒だし忘れてしまうかもしれません。
それを一括で可能にする方法がありました!

  [AutoValidateAntiforgeryToken]
  public class HomeController : Controller
  {

Controllerのクラス定義の上に [AutoValidateAntiforgeryToken]を記述することで
全てのPOSTアクションに適用することができます。
もちろんGETアクションにはそのまま利用できます。

このへんはさすがによくできていて、簡単に実装することができます。
もう一から自力で、フルスクラッチで開発しようなどと
とても思わないですね。