tekitoumemo’s diary

思ったことを書くだけ。長文版Twitter

AutoValidateAntiforgeryTokenが便利すぎて感動した

f:id:tekitoumemo:20180515005125j:plain

今の現場がレベル高くて全く付いていけなくてひたすら給料泥棒してます、よくクビにならんわな。

みんなの洋楽ランキングを絶賛移行中で色々ネタが増えました。それが.Net Coreから追加されたCRLF対策のAutoValidateAntiforgeryTokenで最強だと知りました。あんまりよくわからないけどセキュリティ対策はシンプルに明示的にやる必要ないって考えから出来たみたい。
github.com

今までのValidateAntiForgeryTokenはこんな感じで実装してました。

Controller側

using Microsoft.AspNetCore.Mvc;
[ValidateAntiForgeryToken]
public ActionResult Index ( {
 ....

}

cshtml側

...
@Html.AntiForgeryToken()
 ....

これでクライアント側でトークンを発行してトークンが違ったら400(Bad Request)になるという仕組み。別にこれでも特に問題がなかったのですが、もっと便利な仕組みが.Net Coreで追加されました。

それがAutoValidateAntiforgeryTokenです。
AutoValidateAntiforgeryToken属性が追加Postアクションはクライアント側(html)で明示的に指定しなくてもトークン認証をしてくれるものです。どういうことかというと以下の通り。
Controller側

using Microsoft.AspNetCore.Mvc;
[AutoValidateAntiforgeryToken]
public ActionResult Index ( {
 ....

}

cshtml側

...
// なにもいらないよ!!
 ....

これで以下のhtmlが生成されます。

<input name="__RequestVerificationToken" type="hidden" value="CfDJ8HJB_Oodagkasdfghjgk">

Attributeなのでクラスにつけられるので素敵すぎる!私はベースのコントローラー作ってそれを継承して使ってるので全てのコントローラーに適応されます。以下みたいな感じ。
BaseController

[AutoValidateAntiforgeryToken]
public class BaseController : Controller {
 .........
}

HomeController

public class HomeController : BaseController {
 .........
}

セッション操作などBaseControllerを用意すると便利?(設計のことはわからないけれども)なのでAutoValidateAntiforgeryTokenも付けられるとめっちゃ使い勝手がよくて最高でした。ajaxとか使ってても以下のような感じでいけると思うのでそこまで困らないかなと思います(試してないけど

var token = $('input[name="__RequestVerificationToken"]').val();
 
$.ajax({
    url: '/home/index/',
    type: 'POST',
    data: {__RequestVerificationToken: token},
    dataType: 'JSON',
    success: function (data) {
        console.log(data);
}

【ASP.NET MVC Core】.NET CoreでSystem.Net.Mail.SmtpClientが使えなくなったのでMimeKitを使う

f:id:tekitoumemo:20180510224948p:plain

みんなの洋楽ランキングを.NET Coreに絶賛移行中でMacBook Airのみで開発しています。.Net Core3.0ではWindows Formも対応するみたいでなかなか最強感出てきましたね!ASP.NET CoreはScalaの1.7倍のパフォーマンスを出すそうです。
www.ageofascent.com

タイトル通り「.NET CoreでSystem.Net.Mail.SmtpClientが使えなくなった」です。

.NET CoreからSystem.Net.Mail.SmtpClientでなくMimeKitが推奨されましたのでそのメモ。結構この記事は多いのですが、デバッグ時にSSL推奨の影響でエラーになっちゃったりするんでその対応を追記します。

MimeKitとは?

github.com
オープンソースのメールライブラリです。公式がこう発表しています。

「SmtpClientとそのタイプのネットワークは設計が不十分であり、代わりに MailKitを使うことを強く推奨する」

という感じです。

MimeKitをインストール

バージョンは以下で確認してくださいGithubの最新は2.02だった。なぜ?
www.nuget.org

以下のコマンドで追加

dotnet add package MailKit --version 2.0.3

csprojに

<PackageReference Include="MailKit" Version="2.0.3" />

を追加して

dotnet restore

でも良いです。

使い方

usingと生成

using MimeKit;
var emailMessage = new MimeMessage ();

送信元とユーザー名を設定

emailMessage.From.Add (new MailboxAddress ("ユーザー名", "送信元"));

送信先の設定

emailMessage.To.Add (new MailboxAddress ( "送信先"));

件名と本文を設定

emailMessage.Subject = "件名";
emailMessage.Body = new TextPart ("plain") { Text = "本文" };

SMTPサーバに接続

var client = new SmtpClient();
await client.ConnectAsync("smtp.test.net", 587, SecureSocketOptions.SslOnConnect);

SMTPサーバ認証IDとパスワード)

await client.AuthenticateAsync("SMTPサーバ認証ID", "パスワード");

メールを送信

await client.SendAsync(emailMessage);

SMTPサーバ接続を切る

await client.DisconnectAsync(true);

処理はこれで終了です。ですが、開発中にSSLにしていないとエラーになります。
エラーになるコードは以下です。

var emailMessage = new MimeMessage ();
//と
await client.ConnectAsync("smtp.test.net", 587, SecureSocketOptions.SslOnConnect);

で以下のエラーが発生します。

SslHandshakeException: An error occurred while attempting to establish an SSL or TLS connection.

SSL接続が出来ないってことですね。なので対策のため、生成前に証明書検証用コールバックを設定します。

ServicePointManager.ServerCertificateValidationCallback +=
                (sender, cert, chain, sslPolicyErrors) => true;
var emailMessage = new MimeMessage ();

SecureSocketOptionsはStartTlsを設定します。StartTlsは受信側がTLS/SSLに対応していれば暗号化して送り、されていなければ平文で送るオプションです。

await client.ConnectAsync("smtp.test.net", 587, SecureSocketOptions.StartTls);

ちなみにGmailは以下のコードにメールアドレスとパスワードを設定すればよいです。

await client.AuthenticateAsync("SMTPサーバ認証ID", "パスワード");

ざっと通しで作ったので確認してみてください。

using System.Linq;
using System.Net;
using System.Threading.Tasks;
using MailKit.Net.Smtp;
using MailKit.Security;
using MimeKit;

namespace Mail {
    public class MailManager {

        public string UserName { get; set; }
        public string PassWord { get; set; }
        public string From { get; set; }
        public string To { get; set; }
        public string Subject { get; set; }
        public string Body { get; set; }
        public string Host { get; set; }
        public int Port { get; set; }

        public async Task SendEmailAsync () {

#if DEBUG
            ServicePointManager.ServerCertificateValidationCallback +=
                (sender, cert, chain, sslPolicyErrors) => true;
#endif
            var emailMessage = new MimeMessage ();

            emailMessage.From.Add (new MailboxAddress (this.UserName, this.From));

            emailMessage.To.Add (new MailboxAddress (this.To));

            emailMessage.Subject = this.Subject;

            emailMessage.Body = new TextPart ("plain") { Text = this.Body };

            using (var client = new SmtpClient ()) {
#if DEBUG
                await client.ConnectAsync (this.Host, this.Port, SecureSocketOptions.StartTls);
#else
                await client.ConnectAsync (this.Host, this.Port, SecureSocketOptions.SslOnConnect);
#endif
                await client.AuthenticateAsync (this.UserName, this.PassWord);
                await client.SendAsync (emailMessage);
                await client.DisconnectAsync (true);
            }
        }
    }
}

最低限必要なものはプロパティで用意しているので、設定すれば動くと思います。

async、awaitが標準になり使い勝手も変わらないので結構楽でよいですね!

【Jasmine、TypeScript】JasmineのSpyOnでsubscribe内のメソッドが検知出来ない

https://cdn-ak.f.st-hatena.com/images/fotolife/o/ochimusha01/20170827/20170827055211.jpg

最近、SPAが流行っていて私もAngular5に四苦八苦しています。Angularの(っていうかJSの)テストフレームワークの「Jasmine」でテストしながら開発を進めているのですが、早速詰んだので解決方法を書きます。

「JasmineのSpyOnでsubscribe内のメソッドが検知出来ない」とはどういう状況?

以下のコードからngOnInitでroute.eventsのsubscribeで呼び出しているscrollを検知したいので、テスト側でSpyOnして実行します。が、これが失敗します。alertで呼び出されていることがわかるので実際には動いているはずです。

コード側
public ngOnInit() {
    this.route.events.subscribe(event => {
        if (event instanceof NavigationEnd) {
            window.scroll(100, 100);
            alert("ここ呼ばれているはずだよ!!");
        }
    })
}
テスト側
import { assert } from 'chai';
import { CounterComponent } from './counter.component';
import { TestBed, async, ComponentFixture } from '@angular/core/testing';
import { RouterModule , Router, NavigationEnd ,Routes } from "@angular/router";
import {APP_BASE_HREF} from '@angular/common';
let fixture: ComponentFixture<CounterComponent>;

const appRoutes: Routes = [
    {path: 'counter', component: CounterComponent}
  ];

describe('Counter component', () => {
    beforeEach(() => {
        TestBed.configureTestingModule({
            declarations: [CounterComponent],
            imports: [
                RouterModule.forRoot(appRoutes)
              ],
              providers: [{provide: APP_BASE_HREF, useValue : '/' }]
        })
        fixture = TestBed.createComponent(CounterComponent);
        fixture.detectChanges();
    });

    it('test', async(() => {
        let router: Router;
        router = TestBed.get(Router); 
        spyOn(window, "scroll");
        router.navigate(["/counter"]);
        fixture.componentInstance.ngOnInit();
        expect(window.scroll).toHaveBeenCalled();

    }));
});

実行すると呼び出されていないよというエラーが出ます。

Expected spy scroll to have been called

これは単純にexpectが呼び出されるときにsubscribeが動いていないってことでした。subscribeは実行後の後続処理的なやつで非同期で動きます。なので、expectしたときにまだ動いてなくて検知出来なかっただけというオチでした。

以下が変更点です。

it('test', fakeAsync(() => {
    let router: Router;
    router = TestBed.get(Router); 
    spyOn(window, "scroll");
    router.navigate(["/counter"]);
    fixture.componentInstance.ngOnInit();
    tick(); // or flushMicrotasks()
    expect(window.scroll).toHaveBeenCalled();

}));

fakeAsyncで定義してtick()で処理を待ちます。flushMicrotasksでも良いですが、tickで待ってから評価した方が良い気がします。

むずいわ。

macで.NET CoreのSPAプロジェクトを動かすと謎のエラー

macでもコマンドベースだとSPAがちゃんと動きます。以下の通り。

dotnet new angular -o angularTest
npm i
webpack
dotnet restore
dotnet build
dotnet run

これで動くのでvscodeデバッグしたら以下のエラー。。
f:id:tekitoumemo:20180428161901p:plain
意味不明なエラーが出た、ubuntuでは動いたのに。。
発生したコード

app.UseWebpackDevMiddleware(new WebpackDevMiddlewareOptions
{
    HotModuleReplacement = true
});

例外メッセージ

例外が発生しました: CLR/System.AggregateException
An exception of type 'System.AggregateException' occurred in System.Private.CoreLib.dll but was not handled in user code: 'One or more errors occurred.'
 Inner exceptions found, see $exception in variables window for more details.
 Innermost exception 	 System.ComponentModel.Win32Exception : No such file or directory
   at System.Diagnostics.Process.ResolvePath(String filename)
   at System.Diagnostics.Process.StartCore(ProcessStartInfo startInfo)
   at System.Diagnostics.Process.Start()
   at System.Diagnostics.Process.Start(ProcessStartInfo startInfo)
   at Microsoft.AspNetCore.NodeServices.HostingModels.OutOfProcessNodeInstance.LaunchNodeProcess(ProcessStartInfo startInfo)

これは、angular(フロントJS)をIISで動かそうとしてるのでファイルがねーぞって言われているエラーです。
nodeの環境変数を設定してるのに!
f:id:tekitoumemo:20180428163557p:plain

いい記事を発見。

GUI applications on the Mac do not use inherit any environment variables that are defined for the terminal neither do they run any bash profile scripts. So option 2 is probably the best option.

VS Mac adds the following two paths to its PATH environment variable.

/usr/local/bin
/Library/Frameworks/Mono.framework/Commands
I do not believe there are any plans to have VS Mac somehow execute the bash profile script.

VS2017 for Mac - UseWebpackDevMiddleware crash on application start - Developer Community

macGUIアプリケーションは.bash_profileに設定した環境変数を継承しません。なのでアプリ側にnodeのPATHを設定しないとnodeがないと言われちゃうみたいです。これはmacの仕様なので諦めてね♡ってことみたい。

VSCodeではlaunch.jsonに記載すればおっけいです。vscode環境変数は「env」なのでそこに設定してあげましょう。

"env": {
    ...
    "PATH": "/Users/{自分の名前}/.nodebrew/current/bin" // 参考なので設置したパス
},

で実行。

f:id:tekitoumemo:20180428163044p:plain

macでも環境できてよかったわー。

【C#】テキトーなAutoMapperを作った


AutoMapperがあまり使い勝手良くなかったから自分で作りました。クラスの型が違っててもだいたいマップするよってライブラリ。


メソッド

これだけ(笑)

void Map<T1, T2>(T1 src, T2 dest)
  • T1:コピー元のクラス型
  • T2:コピー先のクラス型
  • src:コピー元のモデル
  • dest:コピー先のモデル

結果はdestにsrcの内容がコピられるってだけです。

使い方

var suitableMapper = new SuitableMapper();
suitableMapper.Map<Test1, Test2>(model1, model2);

どうなるかは以下で説明します。

// Test1クラスのプロパティと値
hoge1 = "TEST1"; // string型
hoge2 = 1; // int型
hoge3 = true; // bool型
hoge4 = new Hoge(){ hoge1 = "OK!"}; / / Hogeクラス型

// Test2クラスのプロパティと値
hoge1 = "TEST2"; // string型
hoge2 = 5; // int型
hoge3 = 10; // int型
hoge4 = null / / Hogeクラス型

// 実行
suitableMapper.Map<Test1, Test2>(model1, model2);

// Test2クラスにコピーされます。
hoge1 = "TEST1"
hoge2 = 1
hoge3 = 10
hoge4 = new Hoge(){ hoge1 = "OK!"}

Test2クラスのhoge3だけは型が違うのでコピーされず、他の3つはTest1クラスのデータがコピーされます。

なぜ作ったか?

AutoMapperは完全に同じクラスじゃないとマッピングしないので、結局手動でマップしたりAutoMapperに型を合わせたりなどいろいろめんどくさいことがあります。画面とデータは対じゃないのでそこの間を吸収出来ないかと思ったことがきっかけです。とりあえず名前と型が一致してたらコピーしちゃえという適当AutoMapperと思ってくれれば良いです。

使えないところ

名前と型が完全に一致してなければダメです。例えばIDとHatenaIDは同じでマップしたくても名前が違うのでダメです。あとクラスは参照型なのでコピーされたらコピー前のデータが残りません。あっあと.NetCoreで作ったので.NetFrameworkでは使えませんが、ソースをコピれば使えます。

注意

テストとかしてないので、使えそうなところを参考程度に見てもらえるとありがたいです。いつかgithubのReadmeもちゃんと書きます。

ドメイン変更でやったことまとめ

みんなの洋楽ランキング
みんなの洋楽ランキング
先月公開した「みんなの洋楽ランキング」のドメイン変更を行いました。公開して2週間なので、大した影響はないのですが一応ちゃんとやりましたので記事に残しておきます。

ちなみに、以下にように変更しました。
https://mygkrnk.azurewebsites.net ⇛ https://mygkrnk.com/

Azureドメインから変更する感じですね。よく「WEBサービスを作った」的な記事を見てるんですが、herokuドメインのままだったりとドメイン変更やSSL導入に抵抗があるように感じます。ドメイン変更は早い段階でやらないと厄介なので早めにやっちゃいましょう!

まずは費用

1000円程度です。お名前.comで取得したドメイン代ぐらいです。

ドメインを取ります

www.onamae.com
僕はお名前ドットコムで取りましたがどこでもよいです、別に事業所によって変わったりしないので。「.com」なら一年で900円ぐらい。ちゃちゃっと購入しましょう。

SSL導入

以前に記事を書いたので、参考になれば。
tekitoumemo.hatenablog.com
今年の3月からLet’s EncryptでワイルドカードSSLが無料で取得出来るようになりました。この団体は素敵なスローガンを掲げてます。

Let's Encrypt を運営している Internet Security Research Group (略称:ISRG) は、アメリカ合衆国カリフォルニア州にある公益法人で、アメリカ合衆国内国歳入法 Section 501(c)(3) による非課税法人として、アメリカ合衆国内国歳入庁 (IRS) による承認を受けています。

インターネットを介した安全な通信を行う際の、経済面・技術面・教育面での障壁を減らすことが、ISRG の使命です。

ISRG は、誰もがより安全なインターネットがに興味を持つことで、公共の利益のためのデジタル基盤を提供する取り組みを一緒に行うことを可能にするという模範を示すことができると信じています。

素晴らしい!!
さらにGooogleのSSL優遇は必須になってきました。
webmaster-ja.googleblog.com

レンタルサーバーやPaasにDNSレコードを設定する

f:id:tekitoumemo:20180417212055p:plain
ココらへんは事業所にしたがってやって下さい。Azureは以前に記事を書いたので参考になれば。
tekitoumemo.hatenablog.com

【超重要】リダイレクト設定

超需要です。リダイレクト設定は必ず行いましょう。Linuxなら.htaccessWindowsならWeb.config、appsettings.json に記載すればよいです。またこれをやらないと後で説明するSearch Consoleのアドレス変更が出来ませんので必須です。Windows Severのリライトの方法は以下(コピペなので参考程度に)

    <rewrite>
      <rules>
          <rule name="mygkrnk.azurewebsites.net" stopProcessing="true">
              <match url="(.*)" />
              <conditions>
                  <add input="{HTTP_HOST}" pattern="^mygkrnk\.azurewebsites\.net$" />
              </conditions>
              <action type="Redirect" url="https://mygkrnk.com/{R:1}" redirectType="Permanent" />
          </rule>
      </rules>
    </rewrite>

【重要】カノニカルタグを設定

同じサイトでドメインが複数あると重複コンテンツとして評価されてSEOが下がってしまいます。なので、どのサイトが本当のドメインか教えたあげる必要があります。記述方法はヘッダータグに本当のURLを入れます。

<head>
<link rel="canonical" href="http://example.com/">
</head>

【超重要】Search Consoleでアドレス変更の申請

Googleの評価を下げたくなければ絶対にやる必要があります。やり方は設定ボタンの「アドレス変更」をクリックします。
f:id:tekitoumemo:20180417214002p:plain
そしたらそれぞれの項目をチェックします。

  • リストから新しいサイトを選択する

取得したドメインを選択して下さい。取得したドメインはSearch Consoleに追加して下さい。

  • 301 リダイレクトが正常に動作していることを確認する

「【超重要】リダイレクト設定」を参考にしてください。

  • 確認方法がまだ残っていることを確認する

変更前と変更後のドメインが確認できれば良いと思います。

  • アドレス変更のリクエストを送信する

上記がすべてOKの場合、送信を押します。
f:id:tekitoumemo:20180417214054p:plain

【やったほうが良い】サイトマップを作成する

「みんなの洋楽ランキング」では、サイトマップを自動生成しています。いつでも自動で作成出来ることによって、ページを更新する度にクローラーが来てくれるので、ここは早めにやっちゃいましょう!Fetch as Googleは1ページのみのインデックスリクエストで、サイトマップはサイト全体のリクエストになります。そのため、コンテンツサイトなど全体のページを見てくれるのでてっとり早いです。
f:id:tekitoumemo:20180417214821p:plain

【やったほうが良い】周囲に告知

仕事なら職場の人や利用してくれる人への告知。個人ならTwitterなどの告知など手段はあるので、状況に合わせて実施します。私はTwitterで告知しました。

これでGoogleインデックスの引き継ぎやその他もろもろが完了です。

安全に出来てよかった。

ubuntuでログインループ

詰んだ。対処したのでメモ。

こんな症状が起きた。
youtu.be
なんで?Nvidiaのドライバを入れたら起きるとのことですが、Nvidiaのドライバなんぞ入れてません。

僕の場合はデスクトップ環境が何らかの原因で壊れたことによってログイン出来なかったみたいです。
対処法にlightdmが書いてあったので指示にしたがって対応。
デスクトップからコンソールモードに変更するためctrl + alt + f1を入力。

パッケージ取得

$ sudo dpkg-reconfigure lightdm

削除&インストール

$ sudo apt-get purge lightdm && sudo apt-get install lightdm

再起動して、ログイン。

完了。

参考
www.computersnyou.com