気軽に請求書を作成できるサービス(Girafa)を公開しました

こんにちは。ビーグルソフトの真鍋です。

このたび、社内で利用していました請求書作成サービス(Girafaといいます)の機能の一部を公開するしました!

f:id:beaglesoft:20181119071621p:plain 新規に請求書を作成 Girafa

guida.girafa.info

Girafaの特徴

  • できることは請求書を作成してPDFとしてダウンロードするだけの機能しかありません。
  • 入力した内容がプレビュー画面にすぐに反映されます。プレビュー画面の内容がPDFの内容となるため入力した内容とダウンロードしたPDFが違うということがなくなります。
  • 請求書の宛名を含めることができるので郵送で宛名を用意する必要がありません。

簡単な請求書であればぱぱっと作成できるのでご利用いただければと思います。また、こちらのご利用は無料となっています。

操作イメージ

f:id:beaglesoft:20181119070811g:plain
請求書を作成してみます

さいごに

請求書の発行から郵送まで行ってくれるサービスは世の中にたくさんあります。とはいえ、もっとライトに請求書の発行を行いたいと言うシーンも有るのではないかと思います。

そんなちょっと請求書を発行したいときに利用していただければと思います。

もし何かお問合せがございましたらこちらのフォームからご連絡ください。

docs.google.com

SerilogでログレベルをSkinごとに設定する

こんにちは。ビーグルソフトの真鍋です。

ASP.NET MVC Coreもバージョンが2.1になってちょっと盛り上がってきた感があるようなないような感じですが、いろいろとエコシステムもこなれてきて大分何をするにも困らない状況になってきた感じがします。今回はログの出力でログレベルを出力先ごとに変更するための設定について調べたものを簡単にまとめました。

Serilogでログレベルを変更する

Serilogで指定したログレベルをSkinごとに設定するためには以下の通り、各SkinでrestrictedToMinimumLevelを指定します。

Log.Logger = new LoggerConfiguration()
    .Enrich.FromLogContext()
    .MinimumLevel.Debug()
    .WriteTo.Console()
    .WriteTo.Slack(SlackUrl, restrictedToMinimumLevel: LogEventLevel.Information)
    .CreateLogger();

Log.Information("GirafaBillBatch start!");
Log.Debug("sample");
Log.Information("GirafaBillBatch end!");

上記の設定の場合には、すべてのSkinでDebugレベル以上の出力を行うように設定しています。その結果、Console SkinについてはDebugレベル以上のログが出力されます。

一方でSlack Skinについては引数でInformationレベル以上の出力を行うように設定しているため、Informationレベル以上のログしか出力されません。

Consoleヘのログ出力

Image from Gyazo

Slackへのログ出力

Image from Gyazo

おわりに

通常のログ出力とは異なり、どうしても通知したいログ、例えばエラーログのみをSlackに通知するということができるのはちょっとしたプログラムでは便利です。ある程度の規模のシステムの場合には監視の仕組みを入れていると思いますが、スクレイピングとか軽量のバッチ処理などは処理結果やエラー時のみSlackで受け取れると便利なことが多いですね。

そういうときに、Skinごと(出力先ごと)にログレベルを変更できるととても便利だなぁと思っていろいろと試してみました。

Adaptive Code ~ C#実践開発手法 第2版 (マイクロソフト関連書)

Adaptive Code ~ C#実践開発手法 第2版 (マイクロソフト関連書)

参考資料

Configuration Basics · serilog/serilog Wiki

ASP.NET MVC Core2でステートレスな構成を作るための設定

こんにちは。beaglesoftの真鍋です。

ASP.NET Core で構築しているレポートシステムの開発が大分すすんで本番環境で稼働させられるところまで来ました。本番環境を構築して社内で利用してみるといろいろな課題画見えてきます。そんな中でちょっと直面した問題とその対応方法をまとめてみました。

利用している構成

  • dotnet core 2.1.301
  • Microsoft.AspNetCore.App 2.1.1

概要

ASP.NET MVC Coreで複数台構成の環境で運用するときに、CSRFトークンなど共通のキー情報により生成される情報はロードバランサーのSticky Sessionを利用する必要があります。

これはリクエストごとに生成されるキー情報がサーバーごとに異なるため、セッションを発行元と紐付ける必要があるためです。そのため、ロードバランサーがCookieから転送するサーバーを決定しセッションごとにサーバーを固定化します。

この構成の場合、ECSなどのステートフルな構成を前提としている運用ではコンテナのライフサイクルとリクエストのライフサイクルが一致しないためうまく運用ができません。

そのため、サーバー群で使用するキー情報を共有してリクエストごとに生成されるキー情報をどのサーバーでも同様に扱えるようにすることでステートレスな構成とすることができます。

ステートレスな構成とするためには、各サーバーが利用するキー情報を1カ所の共有ストレージに安全に保存する必要がありますが、その方法を実現するために提供されているライブラリがASP.NET Core Data Protectionとなります。

ASP.NET Core Data Protection | Microsoft Docs

また、ASP.NET Core antiforgeryについては下記の通りASP.NET Core Data Protectionを利用していることが明記されています。

ASP.NET Core implements antiforgery using ASP.NET Core Data Protection. The data protection stack must be configured to work in a server farm. See Configuring data protection for more information.

Prevent Cross-Site Request Forgery (XSRF/CSRF) attacks in ASP.NET Core | Microsoft Docs

設定方法

今回はRedisにキー情報を保存することにしました。具体的な設定方法は以下の通りとなります。

Key storage providers in ASP.NET Core | Microsoft Docs

CSRFを利用する場合に、トークン生成のキー情報を複数台構成のサーバーで共有するためには以下の設定を行います。

using Microsoft.Extensions.DependencyInjection;
using StackExchange.Redis.Extensions.Core;
using StackExchange.Redis.Extensions.Core.Configuration;
using StackExchange.Redis.Extensions.Newtonsoft;
using System;
using Microsoft.AspNetCore.DataProtection;
using StackExchange.Redis;

namespace net.girafa
{
    public partial class Startup
    {
        private void InitializeRedisCache(IServiceCollection services)
        {
            var redisHost = Configuration["RedisCache:host"];
            
            ...
            var redis = ConnectionMultiplexer.Connect(redisHost);
            services.AddDataProtection().PersistKeysToRedis(redis, "DataProtection-Keys");
        }
    }
}

このように設定することでRedisには以下の情報が生成されるようになります。(RedisCLIで確認しています。)

127.0.0.1:6379> keys DataProtection-Keys
1) "DataProtection-Keys"

127.0.0.1:6379> type DataProtection-Keys
list

127.0.0.1:6379> lrange DataProtection-Keys 0 1
1) "<key id=\"9b0ac354-...\" version=\"1\"><creationDate>2018-08-26T10:44:32.112617Z</creationDate><activationDate>2018-08-26T10:44:32.084794Z</activationDate><expirationDate>2018-11-24T10:44:32.084794Z</expirationDate><descriptor deserializerType=\"Microsoft.AspNetCore.DataProtection.AuthenticatedEncryption.ConfigurationModel.AuthenticatedEncryptorDescriptorDeserializer, Microsoft.AspNetCore.DataProtection, Version=2.1.1.0, Culture=neutral, PublicKeyToken=..\"><descriptor><encryption algorithm=\"AES_256_CBC\" /><validation algorithm=\"HMACSHA256\" /><masterKey p4:requiresEncryption=\"true\" xmlns:p4=\"http://schemas.asp.net/2015/03/dataProtection\"><!-- Warning: the key below is in an unencrypted form. --><value>0pp...5A==</value></masterKey></descriptor></descriptor></key>"

動作確認

動作確認はECS上でALBのStickeySessionをオフにしてから2台のコンテナを用意して実行しました。1台にリクエストが来ていることを確認してからそのコンテナを停止して処理を継続できることを確認します。

上記設定を行うことで実際に処理を継続できることを確認しました。

参考

以下のドキュメントを参考にしました。

asp.net core - How do I handle ValidateAntiForgeryToken across linux servers - Stack Overflow

ASP.NET Core Data Protection | Microsoft Docs

Key storage providers in ASP.NET Core | Microsoft Docs

Prevent Cross-Site Request Forgery (XSRF/CSRF) attacks in ASP.NET Core | Microsoft Docs

Effective C# 6.0/7.0

Effective C# 6.0/7.0