Home –  未分類
Category Archives: 未分類

ASP.NET Core のValidationエラメッセージを一括で日本語化する

最初にですが、.NET初心者なので間違っていましたら申し訳ないです。
また正しい日本語化の方法がありましたら是非教えて頂きたいです_| ̄|○

https://docs.microsoft.com/en-us/aspnet/core/fundamentals/localization

のURLを参照しながら、ローカライゼーションをしてみたところ、アノテーションで設定したRequireAttributeなどのエラーメッセージが日本語化できなかったのです。
色々検索してみても、アノテーションのパラメータのErrorMessageやErrorMessageResourceTypeを使うように書いてあるのですが・・・・

共通のメッセージをわざわざ個別に設定するのはおかしくないですか!?

方針

怠惰なプログラマとしてはありえないと思いまして、もう少し調べてみましたら

Customization And Localization Of ASP.NET Core MVC Default Validation Error Messages


という記事を見かけまして、この記事の通りにしてみたいと思います。

こちらの記事では、ModelMetadataDetailsProvidersを設定することにより各Attributeに対してErrorMessageResourceType、ErrorMessageResourceNameを強制的に一括で設定できるようです。

未確認ではありますが、多分多言語化にはならないので注意してください。

手順

基本的にこちらの記事通りですが、一部省略しています。

リソースファイルの作成

下記のようにResourcesフォルダを作成し、その中にSharedResource.resxという名前でリソースファイルを作成します。

folder20171024.png

少しだけしか登録していませんが下記のように文字列リソースを定義してきます。アノテーションのクラスの名前をキーとしてエラーメッセージを定義してきます。

resource20171024.png

CustomValidationMetadataProviderクラスの作成

このクラスでアノテーションで指定した属性のエラーメッセージを変更します。
リソースファイルには属性名称(例: RequireAttribute)で名前付けされたテキストが取得、設定します。
リソースファイルに対象の文言がなければデフォルト(英語)表示されるようにします。

public class CustomValidationMetadataProvider : IValidationMetadataProvider
{
    private ResourceManager resourceManager; private Type resourceType;
    public CustomValidationMetadataProvider(Type type)
    {
        resourceType = type;
        resourceManager = new ResourceManager(
            type);
    }
    public void CreateValidationMetadata(
        ValidationMetadataProviderContext context)
    {
        foreach (var attribute in context.ValidationMetadata.ValidatorMetadata)
        {
            ValidationAttribute tAttr = attribute as ValidationAttribute;
            if (tAttr != null && tAttr.ErrorMessage == null
                    && tAttr.ErrorMessageResourceName == null)
            {
                var name = tAttr.GetType().Name;
                if (resourceManager.GetString(name) != null)
                {
                    tAttr.ErrorMessageResourceType = resourceType;
                    tAttr.ErrorMessageResourceName = name;
                    tAttr.ErrorMessage = null;
                }
            }
        }
    }
}

Startup.csの変更

services.AddMvcでオプションを設定します。この設定によりCustomValidationMetadataProviderの設定、また属性のアノテーション以外で表示されるエラーメッセージを設定します。

なお、Resources.SharedResourceは一番最初に指定したリソースのクラスとなります。

public void ConfigureServices(IServiceCollection services) =>
    services.AddMvc(o =>
    {
        o.ModelBindingMessageProvider.SetValueMustNotBeNullAccessor( (value) => {
            return string.Format(Resources.SharedResource.ValueMustNotBeNull, value);
        });
        o.ModelMetadataDetailsProviders.Add(
            new CustomValidationMetadataProvider(
                typeof(Resources.SharedResource)));
    });

あとは実行するだけ

適当にViewModel作成し、フォームを作成してValidationを行うと、下記のように日本語化されたメッセージが表示されます(´▽`)

form20171024.png

その他、思ったこと

Windows PCもあるので、リソースファイル作成できましたがVisual Studio Codeを使った開発だとリソースファイル作成できないのですね。
せっかくLinuxやMacでも.NET Coreが動作するようになっているのでリソースファイル含めてWindows以外のプラットフォームで開発できるようになって欲しいです。

Tyrell FXでロングライド(200km, 140km)してきました

横浜の赤レンガで行われたサイクルスタイル2017へちょっと遠回りして140kmライド、
その翌週ですが東京湾一周ライド(ワンイチ)をしてきました。

  • 140km

  • ワンイチ

両方共、タイレルFXで走ったのですが、この距離はDEFY4のときでも走ったことがなく初めての距離です。

グループライドとソロライド

今回ワンイチは6人前後のグループライド、サイクルフェスタはソロライドでした。
6人前後のグループライドは初めてで、色々得るものもありましたし思うところも色々ありました。

グループライドでは休憩毎にワイワイと色々な話が出来るのがとても楽しかったです。
それと私は初心者ですので、走り方やらハンドサインの出し方など色々勉強になります。

コレがソロライドですと、休憩のコンビニとかだと頭のなかは「次どこで休憩しようか?」とかライドの進行の事だけで頭一杯です(;・∀・)
あとグループライドでは「あそこにXXXありますよー!」みたいな感じにやりとりがあるのは楽しいですよね(´ω`)

ソロライドはソロライドで気の向くまま止まって写真をパチリ、急に気になって方向に曲がり美味しいもの食べたりそういったことが出来るのも良いと思います。

グループライドは初めてなのでグループライドのことばかりになってしまいますが、ちょっとした反省がありまして。
というのは、ちょっと自分の体力とかそういったものを見誤ったかなぁと。

結果、中盤の最初あたりから徐々に速度が下がりはじめて、品川で0:00を超えてしまいワンイチとしてはDNFとなりました。

ロードバイクの方が多く引きずられる形で速度が上がっていましたが、予め速度を落とし気味に、また休憩時間もライドの時間の長さを見据えて少なめにお願いすればよかったかなと思います。

グループライドではいろんなひとが集まるのでこういった事をちゃんとやりとりとか調整しないとですね(;・∀・)

ロードとミニベロ

やはり30kmぐらいからの速度維持が全然違うのではないのかなと思います。
巡航速度が35km, 30km, 27kmの3種類ぐらいで走っていましたが、明らかに35kmは超しんどい。
当方が貧脚というのもありますが、辛すぎでしばらくしたら速度下げる事をお願いしました(´・ω・`)

でも、同じくミニベロでスイスイ走って行くひともいるので、自分の貧脚ぶりをなおさら自覚します_| ̄|○

ワンイチと140km走ったときの違い

Cyclemeterを見返して気がついたのですが、ソロで走っているときは30km/hまで出ていることが非常に少なく、
逆にワンイチのときはちょいちょいと30km/h以上でていますし、ログ上ではちょぼちょぼと35km/h以上出ている記録があります。

走っているときの速度は距離の長いワンイチの方が速い!?つかれるはずです。
一方、140kmの停止時間が短かったせいなのかそれでも平均速度はワンイチよりちょっとだけ速いのです。
おそらくサイクリングロード等を使用して信号がない距離が多かった為だと思います。

すなわちサイクリングロード最高ということでしょうか(´ω`)

その他

ワンイチも140kmもチョー楽しかったけど、半端なく疲れました。
しばらくロングライドはお腹いっぱいかもしれませんw

ちょっと悔いが残るとすれば、ワンイチの時にもう少し速度が出して走れれば、スタミナがあればと思ったりします。

しょうがないないので坂でも登って鍛えるのも良いのかなぁなんてちょっと考えたりしています。

PATTO BIKEを試乗してきました。

事はGWの話なのでもう一か月近く前になってしまいますが・・・。
あとはGoogle+でつぶやいたものを再編集的にまとめました。

GW中にサイクルドリームフェスタ2017というイベントが開催されていました。
こちらのイベントは試乗+啓蒙活動という感じでしょうか?
個人的には試乗に興味があり行ってきました。

1 2

このイベントには普段あまり試乗できなさそうなPATTO BIKEや5LINKSの自転車がありまして
それらを試乗してきています。

ついたのが15:00で終了1時間前ということで、ほんと試乗をちょろっとしかできませんでしたが・・・
ようやくPATTO BIKEの試乗ができました。

ということで乗った感想を

乗った感じ思ったのは「あ、楽だ」と思ったことです。
乗り心地が柔らかいというのでしょうか。タイヤも451のせいか足を止めた時に慣性の力が効きやすく思います。
406と451で結構違いがあるなぁと感じます。

限界まで踏み込んだわけではないので、街乗りにつかうようなイメージで言えばたぶんTyrell FXよりこっちのほうが乗りやすいのでないかなと思います。
スポーティさというのでしょうか?あんまりふんわりとした物言いでこまっちゃうのですがそういうのはTyrell FXのほうがあるのかな?なんて。

乗り心地のイメージとしてはTyrell FXよりタルタルーガの乗り味に近い感じでしょうか?。

フロントフォーク、ステム周りも独特でその場で調整(角度変更)が出来るようになっていました。
コレによってアップライトなポジションが好きな人は角度を変更してくださいとのことです。

またフロントフォークは折りたたむ部分を貫通する(という言い方が正しいのかな?)ので他の折りたたみ自転車に比べて強度は高いよという話らしいです。
コンポーネントはロードバイクのものを使用していますが、先程のステムも含めて独特なのでステム交換などでロードバイクの物をつかう事はできなさそうです。

トップチューブ長というのでしょうか?シートからハンドルまでの距離はたぶんTyrell FXより短いです。
まぁTyrell FXはDEFY4よりも長いので・・・。

折りたたみに関しては、非常に小さくなるよう頑張っています。
個人的にはそこまで小さくならなくてもいいかななんて最近思うようになって来ちゃったので(笑)
Tyrell FXぐらいでいいやなんて。

あと、よく考えられてるなーと思ったのは実はチェーンリング。
DRIVE LINE 56T/44Tを採用されていますがこれぐらいのチョイスのほうがいいんじゃないかなと思いました。
たぶんPATTO BIKEのユーザーさんは平地メインでしょうし。

試乗用のPATTO BIKEのタイヤは何気に「パナレーサー タイヤ ミニッツライト PT」と思われるタイヤが使われています。
購入時にタイヤとは違う印象になるのかなということだけ注意が必要そうです。

そんなわけで

PATTO BIKEも中々よいなぁと思いました。
ドロップハンドル乗ったことない人でも乗りやすいのではないかなと思います。
Tyrell FX買ってなかったら悩んでいたかもしれません。

ただ、PATTO BIKEにかんしては情報が少なすぎてその辺が躊躇するポイントですね。
Tyrell FXに比べてロードバイク準拠のところも少ないので。

もうちょい売れてくれば情報増えていい感じになるのかなぁ・・・?

ASP.NET CoreでIntegration Test

ASP.NET Coreを細々と勉強しています。ASP.NET Coreでテストはどうやればいいんだろうと思い調べてみました。
特に、URLベースでアクセスしてそのレスポンスに対して精査をするという事をしようと思います。

今回は公式ドキュメントとか若干記述が足りないところがあり、スタックオーバーフローなどをさまよう羽目になりました。
同じような事で困っている人の役に立てたらうれしいかなと思っています(´・ω・`)

下準備

  • Visual Studio for Mac
  • ソリューションを一つ
    • ASP.NET CoreのWebアプリケーションプロジェクト
    • xUnitのテストプロジェクト

今回はついにリリースされたVisual Studio for Macで作業しましたが、別にWindowsでも変わらないと思います。
各プロジェクトは単純に作成してビルドだけしています。

テストプロジェクト側の設定

プロジェクト画像

テストプロジェクト側には、ASP.NET Coreのプロジェクト(TestApp01)を参照しておきます。
また、パッケージとして「Microsoft.AspNetCore.TestHost」を加えます。

最初のつまづき

当初Integration testing | Microsoft Docsを見ながら作業を行ったのですが、どうしてもうまく行かず四苦八苦しました。
このドキュメントのまま作業をすると

  • appsetting.json関連でエラーが発生
  • View(Razor)関連でエラーが発生

といった現象に悩まされました。

これらを参照したところ無事テストが実行出来るようになりました。

とりあえず動作するソース

色々ごちゃごちゃ細かく書くよりも作ったものを書きたいと思います。

using Xunit;
using System.IO;
using System.Threading.Tasks;
using System.Net.Http;
using System.Reflection;
using System.Linq;
using Microsoft.AspNetCore.Mvc.Razor;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.TestHost;
using Microsoft.Extensions.PlatformAbstractions;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.CodeAnalysis;
using TestApp01;

namespace TestApp01Tests
{
  public static class WebHostBuilderExtensions
  {
    private static string ContentPath
    {
      get
      {
        var path = PlatformServices.Default.Application.ApplicationBasePath;
        var contentPath = Path.GetFullPath(Path.Combine(path, $@"../../../../{nameof(TestApp01)}"));
        return contentPath;
      }
    }

    public static IWebHostBuilder ConfigureTestContent(this IWebHostBuilder builder)
    {
      return builder.UseContentRoot(ContentPath);
    }

    public static IWebHostBuilder ConfigureTestServices(this IWebHostBuilder builder)
    {
      return builder.ConfigureServices(services =>
      {
        services.AddMvcCore();
        services.Configure((RazorViewEngineOptions options) =>
        {
          var previous = options.CompilationCallback;
          options.CompilationCallback = (context) =>
          {
            previous?.Invoke(context);

            var assembly = typeof(Startup).GetTypeInfo().Assembly;
            var assemblies = assembly.GetReferencedAssemblies()
                         .Select(x => MetadataReference.CreateFromFile(Assembly.Load(x).Location))
                         .ToList();
            assemblies.Add(MetadataReference.CreateFromFile(Assembly.Load(new AssemblyName("mscorlib")).Location));
            assemblies.Add(MetadataReference.CreateFromFile(Assembly.Load(new AssemblyName("System.Private.Corelib")).Location));
            assemblies.Add(MetadataReference.CreateFromFile(Assembly.Load(new AssemblyName("Microsoft.AspNetCore.Html.Abstractions")).Location));
            assemblies.Add(MetadataReference.CreateFromFile(Assembly.Load(new AssemblyName("Microsoft.AspNetCore.Razor")).Location));
            assemblies.Add(MetadataReference.CreateFromFile(Assembly.Load(new AssemblyName("Microsoft.AspNetCore.Razor.Runtime")).Location));
            assemblies.Add(MetadataReference.CreateFromFile(Assembly.Load(new AssemblyName("Microsoft.AspNetCore.Mvc")).Location));
            assemblies.Add(MetadataReference.CreateFromFile(Assembly.Load(new AssemblyName("System.Runtime")).Location));
            assemblies.Add(MetadataReference.CreateFromFile(Assembly.Load(new AssemblyName("System.Threading.Tasks")).Location));
            assemblies.Add(MetadataReference.CreateFromFile(Assembly.Load(new AssemblyName("System.Dynamic.Runtime")).Location));
            assemblies.Add(MetadataReference.CreateFromFile(Assembly.Load(new AssemblyName("System.Text.Encodings.Web")).Location));

            context.Compilation = context.Compilation.AddReferences(assemblies);
          };
        });

      });
    }
  }

    public class UnitTest1
    {
    private readonly TestServer _server;
    private readonly HttpClient _client;

    public UnitTest1()
    {
      _server = new TestServer(new WebHostBuilder()
        .UseStartup<Startup>()
          .ConfigureTestContent()
                .ConfigureTestServices()
      );
      _client = _server.CreateClient();


    }

        [Fact]
        public async Task Test1()
        {
      var response = await _client.GetAsync("/");
      response.EnsureSuccessStatusCode();

      var responseString = await response.Content.ReadAsStringAsync();

      // Assert
      Assert.Contains("Hello World!",
        responseString);
        }
    }
}

簡単な解説

大半がテスト用のホストの準備です。テスト本体はUnitTest1クラスのTest1メソッドとなります。

UnitTest1クラスのコンストラクタでテスト用のホストとクライアント作成し、それを利用してテストを行います。
コンストラクタでConfigureTestContentConfigureTestServicesが呼ばれています。

これらが上の方で定義しているWebHostBuilderExtensionsクラスで拡張メソッドとして定義されています。

ConfigureTestContent拡張メソッドについて

テストプロジェクトでそのままホストを起動すると、コンテンツのルートディレクトリの位置が変わってしまいます。
そのために、appsetting.jsonファイルなどが読めずにエラーとなります

この拡張メソッドではそのコンテンツルートディレクトの正しい位置を設定するという機能を持っています。

ConfigureTestServicesメソッドについて

上のConfigureTestContent拡張メソッドでコンテンツのディレクトリを正しく設定したとしてもViewの描画でエラーが発生します。
View上で使用している各種クラスがみつからないようです。
.NET初心者でいまいちよくわかっていないのですが、このメソッドにより各クラスのアセンブリを読み込ませる事によりエラーが発生しなくなるということだと思います。

そんなわけで

無事テスト出来るようになったんですが、View関連はもう少しスマートになりませんでしょうかね。。。個人的にテストフレームワーク使ってテストってほとんどやったことないので、何もかもが勉強でした(;・∀・)

鋸山へ行ってきました。

GW中のことですが友人と鋸山へハイキングへ行ってきました。
なんとなく写真も撮ったので書いて見ます。

aaa

ルート

浜金田駅からスタートし当初の予定では車力道で登る予定でしたが、実際には最初の方の分岐を間違え安兵衛井戸と沢コースで登りました。
鋸山頂上からは日本寺経由で保田駅へ向かいました。

現地までの電車

ホーム写真

9時9分、浜金田着の電車で向かいました。君津駅?から先は電車がおおよそ一時間に1本しかありません。
乗り遅れると次は一時間後になりかねないので余裕を持って移動したいところです(´・ω・`)

電車自体はGWという事もあり混雑していました。席はすべて埋まり、立っている人も比較的多いですが都心のラッシュのような混み方ではありません。

近辺にコンビニあるかと思えば軽く目視した感じだとありませんでした。ちょっと話を聞いた感じだとちょっとあるいたところにあるらしいのですが・・・未確認です。

登り

動物写真 洞窟写真 頂上写真

最初に書いた通り、当初は車力道で登る予定でした。最初の分岐で間違え左に曲がり入ってしまった為です。
観光協会かな?地図を頂いたのですが今回歩いた安兵衛井戸と沢コースに関しての記述がわかりづらく勘違いしてしまった模様です。
分岐点にもう少し分かりやすく看板に書いたほうがいいような気がします。健脚向けですとか。

ノコギリ山の地図はとても見やすくていいですね。

実際登り始めると、鋸山でハードなコースの為か人が少なくまた実にハイキングっぽい(観光ぽくない)コースで楽しめました。
途中、子タヌキ?動物がいたりしてちょっと驚きました。
こんな間近にこの手の野良動物を見たのは初めてかもしれません。
また観光的ルートとは違うので軍手があったほうが歩きやすいと思います。

上の写真のように途中真っ暗い30mぐらいのトンネルがあります。私はヘッドライトを持ち歩いていますがiPhoneの懐中電灯機能を使い通り抜けました。
ライトないと真っ暗な感じになりますので短いとはいえライトを持っておいたほうがいいように思います。

このトンネルあたりから東の肩と呼ばれるあたりまではちょっと道がわかりづらく、若干「あれ?これであってる?」とか思いながら歩いたりしています。

頂上は比較的狭く、ゆっくり出来る感じではありませんでした。
休憩だけして日本寺方面に向かいました。

頂上から日本寺まで

途中1 途中2 途中3

鋸山の頂上から日本寺までですが、一旦下り方面へ。
途中、展望台など見晴らしいのいいところなどがあり、登りと違いどんどんと行き交う人数が増えて行きます。

随分下ったなと思う頃、地獄覗きを下から覗けるぐらいになったあと、日本寺方面への分岐があります。

写真はありませんがここから再び登りです(;・∀・)
実のところ頂上通った後でもう下りしかないだろうと思っていた後でしたので結構ショックです_| ̄|○

日本寺の入り口までひたすら階段が続きます。

日本寺

景色1 景色2 景色3

日本寺の拝観料は600円だったかと思います。GWということあり、かなり混んでいました。
日本寺がどういうところかは日本寺の公式サイトを見ていただければと思います。

中に入るとすぐ百尺観音が見え、そこから少し上のほうに上がると有名な地獄のぞきがあります。
今回は一時間半待つよという助言を入り口で聞き、実際に列を見るとすごい・・・・_| ̄|○ il||li

近くからいい景色は取れたので満足して、地獄のぞき自体は素通りしてきました。

そこから保田駅へ向かいます。ここからお寺の中という事で、極めて歩きやすく舗装された道路です。
途中おっきい大仏があり、そこでひと休憩しつつ下ります。

保多駅まで

日本寺でてから保田駅までは約1.5kmほどの工程です。
ほぼ平坦な道のりの為、あとはだらだら歩きました。

駅までのルートに関しては随所に道しるべがあるので迷うことはないと思います。

特筆すべきはなんか猿っぽいのが木の上にいた!
よく見えなかったけどカナリそれっぽかったです。野生の動物をハイキング中に見るのはホント貴重です。

帰りの自動車

実は昼食中々ありつけず食べたのは16:00ぐらいという有様。
そして帰りは友人の自動車で途中まで送ってもらったのですが・・・・、
GWのせいで混雑ひどすぎだろヽ(`Д´)ノプンプン

自動車で高速使ってスィーっと行けるかと思ったら渋滞渋滞で大変さが半端ないです。

鋸山登るときは混んでいる時期は避けて行くことをおすすめします

全体を通しての感想

しょうゆサイダー

とりあえず行く時期を間違えたかなって思っています。GWの超混雑なタイミングでなくての他の5月の休日とかのほうが混雑度が少なくストレスが少なかったのではないかと。

それから観光地的なイメージでしたたが、ルート間違えたせいだと思いますが(笑)意外と登りがいがあり楽しかったです。まさか野生動物見れるとも思わなかったので非常に良かったです。

また日本寺からの景色は非常に良かったので行ってよかったと思います。

ただ・・・うちからあまりにも遠いので日帰りシンドイです_| ̄|○

なんか醤油サイダーなるものがあったので飲んでみたのですが、悪くはない味でした。ヤバそうなものを飲んだつもりだったので微妙な気持ちになりました(;・∀・)