検証属性のエラーメッセージを一元管理する

元々ある検証属性のエラーメッセージを一元管理する場合

検証属性に対応するAttributeAdapterを継承し、エラーメッセージのリソースを指定します。

using Resources;
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;

namespace Sample.CustomAttributeAdapter
{
    public class CustomStringLengthAttributeAdapter : StringLengthAttributeAdapter
    {
        public CustomStringLengthAttributeAdapter(ModelMetadata metadata, ControllerContext context, StringLengthAttribute attribute)
        : base(metadata, context, attribute)
    {
            // 作成したリソースファイルを指定
            attribute.ErrorMessageResourceType = typeof(Messages);
            // リソースファイルに記述した名前を指定
            attribute.ErrorMessageResourceName = "PropertyValueStringLength";
        }
    }
}

Global.asaxのapplication_Startでリソースファイルを指定し、作成したアダプターを登録します。

using System.ComponentModel.DataAnnotations;
using System.Web;
using System.Web.Mvc;

namespace Sample
{
    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            // リソース名の設定
            DefaultModelBinder.ResourceClassKey = "Messages";
            // アダプターの設定
            DataAnnotationsModelValidatorProvider.RegisterAdapter(
                typeof(StringLengthAttribute), typeof(CustomStringLengthAttributeAdapter));
        }
    }
}

自作した検証属性のエラーメッセージを一元管理する場合

例えば正しい日付形式かを検証する属性を自作したとします。

using System.ComponentModel.DataAnnotations;
using System.Text.RegularExpressions;

namespace Sample.CustomValidationAttribute
{
    /// <summary>
    /// 入力値が「yyyy/MM/dd」形式であるかを検証します。
    /// </summary>
    public class DateAttribute : ValidationAttribute
    {
        public override bool IsValid(object value)
        {
            if (value != null)
            {
                return Regex.IsMatch(value.ToString(), @"^(\d{4})/(0[1-9]|1[0-2])/(0[1-9]|[12][0-9]|3[01])$");
            }
            return false;
        }
    }
}

この場合、DataAnnotationsModelValidatorを継承し、エラーメッセージのリソースを指定します。
自作の場合は、ErrorMessageをnullにしてください。

using Resources;
using System.Web.Mvc;
using Sample.CustomValidationAttribute;

namespace Sample.CustomAttributeAdapter
{
public class CustomDateAttributeAdapter : DataAnnotationsModelValidator<DateAttribute>
    {
        public CustomDateAttributeAdapter(ModelMetadata metadata, ControllerContext context, DateAttribute attribute)
        : base(metadata, context, attribute)
    {
            attribute.ErrorMessageResourceType = typeof(Messages);
            attribute.ErrorMessageResourceName = "PropertyValueDate";
            attribute.ErrorMessage = null;
        }
    }
}

Global.asaxのapplication_Startでリソースファイルを指定し、作成したアダプターを登録します。

using Sample.CustomValidationAttribute;
using Sample.CustomAttributeAdapter;
using System.Web;
using System.Web.Mvc;

namespace Sample
{
    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            // リソース名の設定
            DefaultModelBinder.ResourceClassKey = "Messages";
            // アダプターの設定
            DataAnnotationsModelValidatorProvider.RegisterAdapter(
                typeof(DateAttribute), typeof(CustomDateAttributeAdapter));
        }
    }
}

あとがき

毎度アダプターをGlobal.asaxに書くのもあれなのでConfigを作った方がいいかもしれません。

using Sample.CustomValidationAttribute;
using Sample.CustomAttributeAdapter;
using System.ComponentModel.DataAnnotations;
using System.Web.Mvc;

namespace Sample
{
    public class AttributeAdapterConfig
    {
        public static void RegisterAttributeAdapters()
        {
            // リソース名の設定
            DefaultModelBinder.ResourceClassKey = "Messages";
            // アダプターの設定
            DataAnnotationsModelValidatorProvider.RegisterAdapter(
                typeof(StringLengthAttribute), typeof(CustomStringLengthAttributeAdapter));
            DataAnnotationsModelValidatorProvider.RegisterAdapter(
                typeof(DateAttribute), typeof(CustomDateAttributeAdapter));
        }
    }
}
using System.Web;
using System.Web.Mvc;

namespace Sample
{
    public class MvcApplication : System.Web.HttpApplication
    {
        protected void Application_Start()
        {
            AttributeAdapterConfig.RegisterAttributeAdapters();
        }
    }
}

検証属性を自作する

ASP.NET MVCが提供している検証属性だけじゃ足りない!!!
そういう場合は自作する必要があります。

つくりかた

ValidationAttributeクラスを継承したクラスを作成し、IsValidメソッドをオーバーライドします。
クラス名は「HogeAttribute」のように末尾にAttributeを付与してください。

using System;
using System.ComponentModel.DataAnnotations;
using System.Text.RegularExpressions;

namespace Sample.CustomValidationAttribute
{
    /// <summary>
    /// 入力値が「yyyy/MM/dd」形式であるかを検証します。
    /// </summary>
    public class DateAttribute : ValidationAttribute
    {
        public override bool IsValid(object value)
        {
            if (value != null)
            {
                return Regex.IsMatch(value.ToString(), @"^(\d{4})/(0[1-9]|1[0-2])/(0[1-9]|[12][0-9]|3[01])$");
            }
            return false;
        }
    }
}

つかいかた

自作した検証は元々ある検証と同じように使用することが出来ます。

using System.ComponentModel;
using System.ComponentModel.DataAnnotations;
using Sample.CustomValidationAttribute;

namespace Sample.Dto
{
    public class PersonViewModel
    {
        [DisplayName("生年月日")]
        [Required]  // これはもともとあるやつ
        [Date]  // これ自作したやつ
        public string BirthDay { get; set; }
    }
}

TypeScript環境構築

今回はVisual Studioを使わない場合のTypeScript開発環境を構築してみます。

TypeScriptのインストール

npmでTypeScriptをインストールします。

npm install typescript -global

これでTypeScriptコンパイラを利用できます。

初期化

プロジェクトルートでまず下記コマンドを実行します。

tsc --init

これでtsconfig.jsonが作られます。

tsconfig.json

{
  "compilerOptions": {
    /* Basic Options */
    "target": "es5",                          /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', or 'ESNEXT'. */
    "module": "commonjs",                     /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */
    // "lib": [],                             /* Specify library files to be included in the compilation:  */
    // "allowJs": true,                       /* Allow javascript files to be compiled. */
    // "checkJs": true,                       /* Report errors in .js files. */
    // "jsx": "preserve",                     /* Specify JSX code generation: 'preserve', 'react-native', or 'react'. */
    // "declaration": true,                   /* Generates corresponding '.d.ts' file. */
    // "sourceMap": true,                     /* Generates corresponding '.map' file. */
    // "outFile": "./",                       /* Concatenate and emit output to single file. */
    // "outDir": "./",                        /* Redirect output structure to the directory. */
    // "rootDir": "./",                       /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */
    // "removeComments": true,                /* Do not emit comments to output. */
    // "noEmit": true,                        /* Do not emit outputs. */
    // "importHelpers": true,                 /* Import emit helpers from 'tslib'. */
    // "downlevelIteration": true,            /* Provide full support for iterables in 'for-of', spread, and destructuring when targeting 'ES5' or 'ES3'. */
    // "isolatedModules": true,               /* Transpile each file as a separate module (similar to 'ts.transpileModule'). */

    /* Strict Type-Checking Options */
    "strict": true                            /* Enable all strict type-checking options. */
    // "noImplicitAny": true,                 /* Raise error on expressions and declarations with an implied 'any' type. */
    // "strictNullChecks": true,              /* Enable strict null checks. */
    // "strictFunctionTypes": true,           /* Enable strict checking of function types. */
    // "noImplicitThis": true,                /* Raise error on 'this' expressions with an implied 'any' type. */
    // "alwaysStrict": true,                  /* Parse in strict mode and emit "use strict" for each source file. */

    /* Additional Checks */
    // "noUnusedLocals": true,                /* Report errors on unused locals. */
    // "noUnusedParameters": true,            /* Report errors on unused parameters. */
    // "noImplicitReturns": true,             /* Report error when not all code paths in function return a value. */
    // "noFallthroughCasesInSwitch": true,    /* Report errors for fallthrough cases in switch statement. */

    /* Module Resolution Options */
    // "moduleResolution": "node",            /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */
    // "baseUrl": "./",                       /* Base directory to resolve non-absolute module names. */
    // "paths": {},                           /* A series of entries which re-map imports to lookup locations relative to the 'baseUrl'. */
    // "rootDirs": [],                        /* List of root folders whose combined content represents the structure of the project at runtime. */
    // "typeRoots": [],                       /* List of folders to include type definitions from. */
    // "types": [],                           /* Type declaration files to be included in compilation. */
    // "allowSyntheticDefaultImports": true,  /* Allow default imports from modules with no default export. This does not affect code emit, just typechecking. */
    // "preserveSymlinks": true,              /* Do not resolve the real path of symlinks. */

    /* Source Map Options */
    // "sourceRoot": "./",                    /* Specify the location where debugger should locate TypeScript files instead of source locations. */
    // "mapRoot": "./",                       /* Specify the location where debugger should locate map files instead of generated locations. */
    // "inlineSourceMap": true,               /* Emit a single file with source maps instead of having a separate file. */
    // "inlineSources": true,                 /* Emit the source alongside the sourcemaps within a single file; requires '--inlineSourceMap' or '--sourceMap' to be set. */

    /* Experimental Options */
    // "experimentalDecorators": true,        /* Enables experimental support for ES7 decorators. */
    // "emitDecoratorMetadata": true,         /* Enables experimental support for emitting type metadata for decorators. */
  }
}

tsconfig.jsonコンパイルの詳細な設定を行うことが出来ます。

tsファイルのトランスパイル

例えばapp.tsをコンパイルする場合は下記コマンドを実行します。

tsc app.ts

app.tsからapp.jsが生成されます。

最後に

TypeScript入門 Typings入門

TypeScriptでknockout

TypeScriptでKnockoutを使ってみます。
ただし、下記コマンドが実行できる前提です。
- npm - tsc - typings

環境構築

今回はVisual Studioを使わず、testフォルダ内にサンプルを作ってみます。
testフォルダで下記コマンドを実行し、tsc、typings、npmを実行出来る環境を用意します。

tsc --init
typings init
npm init

型定義ファイルの取得

型定義ファイルを取得しましょう。
今回はKnockoutの型定義ファイルを取得します。

typings instal dt~knockout --global --save

knockout.jsの取得

npmでknockout.jsを取得します。

npm install knokcout 

サンプル実装

まず、ViewModelを作ります。

viewModel.ts

/// <reference path="../typings/index.d.ts"/>

class ViewModel{
    // property
    private privateMsg = "隠しメッセージ";
    
    // ko observable
    private observableMsg = ko.observable("");

    constructor(){
        // ko observableに値をセット
        this.observableMsg("Hello Knockout World!!!");
    }

    /**
     * 隠しメッセージを表示します。
     */
    private showMsg(): void{
        alert(this.privateMsg);
    }
}

次に、ViewModelをバインドします。

app.ts

/// <reference path="../typings/index.d.ts"/>
/// <reference path="viewModel.ts"/>

window.onload = function(){
    const viewModel = new ViewModel();
    ko.applyBindings(viewModel);
}

画面を作ります。
node_moduleを直に参照するのは。。。

index.html

<html>
    <head>
        <script src="node_modules/knockout/build/output/knockout-latest.js"></script>
        <script src="ts/viewModel.js"></script>
        <script src="ts/app.js"></script>
    </head>
    <body>
        <div data-bind="text: observableMsg"></div>
        <button type="button" data-bind="click: showMsg">メッセージを表示する</button>
    </body>
</html>

tsのトランスパイル

tsファイルをjsファイルにトランスパイルします。

tsc app.ts
tsc viewModel.ts

今回は手動でトランスパイルしましたが、gulpやらgruntで自動化するのが望ましいです。
そもそもVisual Studio使えばこんな面倒なことしなくても

画面の表示

早速画面を表示しましょう。

f:id:mt9116:20171208124320p:plain

ボタンをクリックするとViewModelで定義した関数が呼び出されます。

f:id:mt9116:20171208124312p:plain

サンプルの構成

f:id:mt9116:20171208124304p:plain

linq.jsの紹介

そもそもLINQって?

Language-Integrated Queryの略。
ASP.NETで配列をクエリで簡単に操作することが出来ます。

linq.jsとは?

LINQJavaScriptで使えるようにしたライブラリ。
http://neue.cc/reference.htm

linq.js機能紹介

よく使う機能を紹介していきます。

まず適当な配列を用意します。

var list = [
    { "id": 0, "name": "hoge1", "order": 3},
    { "id": 1, "name": "fuga1", "order": 5},
    { "id": 2, "name": "hoge2", "order": 1},
    { "id": 3, "name": "piyo1", "order": 4},
    { "id": 4, "name": "fuga2", "order": 2}
];

Any, Contains

配列に指定した条件のものが含まれるかを調べるのに使います。

// true
Enumerable.From(list).Any(x => { return x.name == "hoge1" });

// false
Enumerable.From(list).Any(x => { return x.name == "hogee" });

// true
Enumerable.From(list).Select(x => { return x.name }).Contains("hoge1");

// false
Enumerable.From(list).Select(x => { return x.name }).Contains("hogee");

OrderBy, OrderByDescending

配列を昇順、降順にソートできます。

// 昇順
Enumerable.From(list).OrderBy(x => { x.order });

// 降順
Enumerable.From(list).OrderByDescending(x => { x.order });

Select, Where

要素の抽出、フィルタに用います。

Enumerable.From(list).Select(x => { return x.id }).ToArray();

Enumerable.From(list).Where(x => { return 1 <= x.id && x.id <= 3 }).ToArray();

動作確認にどうぞー

Knockout入門

knockout.jsって?

データバインド機能を提供するJSライブラリです。
http://knockoutjs.com
非常に軽量で、皆大好き?jQueryと競合しません。
公式サンプルが充実しているので、取っ付きやすいです。

knockout.jsを使ってみる

knockout.jsのダウンロード

http://knockoutjs.com/downloads/index.htmlからダウンロードします。

ViewModelをViewにバインドしてみる

Knockoutではko.applyBindingsでViewにViewModelをバインドさせます。

ViewModel

var viewModel = {
    msg: "Hello Knockout World!"
};
ko.applyBindings(viewModel);

ViewでViewModelのプロパティを表示してみます。

View

<html>
    <head>
        <script type="text/javascript" src="knockout-3.0.0.js"></script>
    </head>
    <body>
        <div data-bind="text: msg"></div>
    </body>
</html>

Hello Knockout World!と表示されます。

ViewModelの更新をViewに伝えてみる

更新するプロパティをobservableとします。
プロパティを取得するには引数なしのobservableを呼びます。 プロパティを更新するには、引数に新しい値を入れobservableを呼びます。

ViewModel

var viewModel = {
    clickedCount: ko.observable(0)
};
ko.applyBindings(viewModel);

window.onclick = function(){
    var cnt = viewModel.clickedCount() + 1;
    viewModel.clickedCount(cnt);
}

View

<html>
    <head>
        <script type="text/javascript" src="knockout-3.0.0.js"></script>
    </head>
    <body>
        Clicked Count: <span data-bind="text: clickedCount"></span>
    </body>
</html>

クリックされたらクリック数が増えていきます。

最後に

以上、簡単ではありますがknockout.jsを紹介しました。
ViewModelを介してViewを更新していくイメージがつかめたでしょうか。
MVVMパターンを試してみたい方はぜひ使ってみてください。

nodistについて

nodistの紹介

nodistって?

Windows向けのnode.jsバージョン管理ツールです。

nodistのインストール

nodist公式ページ から最新版のインストーラを落として、実行するだけです。

nodistの使い方

使用可能なnode.jsの確認

下記コマンドで使用可能なnode.jsのバージョン一覧が表示されます。

nodist dist

...
  6.11.3
  6.11.4
  6.11.5
  6.12.0
  6.12.1
  7.0.0
  7.1.0
  7.2.0
  7.2.1
  7.3.0
  7.4.0
  7.5.0
...

node.jsのインストール

例えば7.1.0をインストールする場合は下記コマンドを実行します。

nodist + 7.1.0

nodeのバージョンを見ると以下のように表示されるはずです。

node -v
v7.1.0

インストールしたnode.jsの管理

まず、インストールしたnode.jsは下記コマンドで表示出来ます。

nodist
6.1.0
> 7.1.0 (global: 7.1.0)

どのバージョンがglobalに指定されているのかが分かります。

node.jsのバージョンを切り替える場合は書きコマンドを実行しましょう。

nodist global 6.1.0

node.jsのバージョンが切り替わるはずです。

nodist
> 6.1.0 (gloval: 6.1.0)
7.1.0

node -v
v6.1.0

最後に

nodistでnode.jsのバージョン管理が出来るのでぜひお試しください。
ただし、もともとnode.jsをインストールしている場合は一旦アンインストールしてからnodistをインストールしてください。