WebSurfer's Home

トップ > Blog 1   |   ログイン
APMLフィルター

日付と通貨の書式設定

by WebSurfer 2019年2月4日 18:10

ASP.NET MVC5 アプリで日付(DateTime 型)と通貨(Decimal 型)を表示し、編集してデータベースを更新する場合、どのように書式設定を行うのが良いかということについて書きます。

日付と通貨の書式設定

上の画像は DisplayFor と TextBoxFor を上下に並べて表示していますが、その「契約日」と「価格」に示したように、

  1. DisplayFor 等を使った表示の場合のみ書式設定を行い、
  2. テキストボックスに値を表示する場合は書式設定しない。または、"{0:F0}" のような書式設定に留めパースできない文字は含めないようにする。

・・・のがよさそうです。

スキャフォールディング機能を使って CRUD 可能なアプリを作ると Index (レコード一覧), Create, Delete, Edit, Details というアクションメソッドとそれに対応するビューが生成されますが、Index, Delete, Details を上記 (1) で、Create, Edit を上記 (2) で表示するということです。

テキストボックスに表示する文字列も 2019年2月4日 とか ¥1,000 のように表示することはできますが、クライアント側での検証を無効にしてサーバーに POST したとしても、DateTime 型や Decimal 型にパースできないので結局モデルバインディングの際の検証が通りません。

どうしてもテキストボックスに表示する文字列も 2019年2月4日 とか ¥1,000 のようにしたいということであれば、モデルのプロパティも含めて全て String 型として扱い、検証は正規表現のみを使って行うということも考えられますが、データベースの型が datetime とか money でしょうから、サーバー側での取り扱いを考えると現実的ではなさそうです。

それに、ユーザーとしても、編集する際にいちいち ¥ とか , を入力するのは煩わしいはずで、喜んではくれないのでは?

上の画像のように表示するための方法ですが、DisplayFor での表示の書式はモデルのプロパティに DisplayFormatAttribute を付与して設定し、テキストボックスには TextBoxFor を使ってその第 2 引数に "{0:yyyy/MM/dd}"、"{0:F0}" などの書式を設定するのがよさそうです。

EditorFor を使うと書式が自由に設定できないし(DisplayFormatAttribute の設定と同じでよければ可能ですが)、レンダリングされる html ソースの input 要素の type 属性が問題になることがありますので使う場合は要注意です。

具体的には以下のようにします。

Model

public class PurchaseRecord
{
  [Display(Name = "ID")]
  [Required(ErrorMessage = "{0} は必須")]
  [RegularExpression(@"^\d{1,5}$", 
      ErrorMessage = "数字 1 ~ 5 文字")]
  public int ID { get; set; }

  [Display(Name = "契約日")]
  [Required(ErrorMessage = "{0} は必須")]
  [RegularExpression(
      @"^\d{4}/\d{2}/\d{2}( \d{1,2}:\d{2}:\d{2})?$", 
      ErrorMessage = "yyyy/MM/dd 形式")]
  [DisplayFormat(DataFormatString = "{0:yyyy年M月d日}")]
  public DateTime ContractDate { get; set; }

  [Display(Name = "価格")]
  [Required(ErrorMessage = "{0} は必須")]
  [RegularExpression(@"^\d{1,5}$", 
      ErrorMessage = "数字 1 ~ 5 文字")]
  [DisplayFormat(DataFormatString = "{0:C0}")]
  public decimal Price { get; set; }
}

View(一部の抜粋)

<div class="form-group">
  @Html.LabelFor(model => model.ID, 
    htmlAttributes: new { @class = "control-label col-md-2" })
  <div class="col-md-10">
    @Html.DisplayFor(model => model.ID)
    @Html.EditorFor(model => model.ID, 
      new { htmlAttributes = new { @class = "form-control" } })
    @Html.ValidationMessageFor(model => model.ID, 
      "", new { @class = "text-danger" })
  </div>
</div>

<div class="form-group">
  @Html.LabelFor(model => model.ContractDate, 
    htmlAttributes: new { @class = "control-label col-md-2" })
  <div class="col-md-10">
    @Html.DisplayFor(model => model.ContractDate)
    @Html.TextBoxFor(model => model.ContractDate, 
      "{0:yyyy/MM/dd}", new { @class = "form-control" })
    @Html.ValidationMessageFor(model => model.ContractDate, 
      "", new { @class = "text-danger" })
  </div>
</div>

<div class="form-group">
  @Html.LabelFor(model => model.Price, 
    htmlAttributes: new { @class = "control-label col-md-2" })
  <div class="col-md-10">
    @Html.DisplayFor(model => model.Price)
    @Html.TextBoxFor(model => model.Price, 
      "{0:F0}",new { @class = "form-control" })
    @Html.ValidationMessageFor(model => model.Price, 
      "", new { @class = "text-danger" })
  </div>
</div>

あと、オマケの話ですが、ASP.NET Web Forms アプリで使う GridView などで書式設定する際も同様なことはできます。以下の画像を見てください。

GridView で日付と通貨の書式設定

赤枠で囲った部分は[編集]ボタンをクリックして編集モードにしたテキストボックスですが、他の行との違いを見てください。

Tags: , ,

MVC

ASP.NET MVC に Web API 追加

by WebSurfer 2018年12月22日 18:04

既存の ASP.MET MVC5 アプリに Web API 2.2 のコントローラを追加してみました。その方法を備忘録として書いておきます。需要はないかもしれませんが。(笑)

MVC + Web API プロジェクト

既存の MVC5 アプリは VS2015 のテンプレートで生成した .NET Framework v4.6.1 ベースの単独 MVC プロジェクトで、ASP.NET Identity を利用してクッキーベースの認証を行っています。

その既存の MVC5 アプリに Web API の機能を追加するのですが、Web API の認証は既存のクッキーベースとするのではなく、Web API で推奨されているトークンベースとします。その際、既存の MVC5 アプリが持つ ASP.NET Identity からユーザー情報を得てトークン認証を行うようにします。

基本的には、下の画像のように VS2015 の Web API テンプレートを使ってプロジェクトを新規に作り、それから必要な部分を既存の MVC プロジェクトに追加していくという感じです。

Web API プロジェクトの作成

Web API プロジェクトを作った後、以下の手順で、既存の MVC プロジェクトに必要なパッケージ、コードを追加します。

(1) SSL 有効化

開発環境でも SSL 通信下で検証ができるように、先の記事「IIS Express で SSL 通信」に従って IIS Express で SSL 通信を利用できるように設定します。

(2) NuGet パッケージのインストール

既存の MVC プロジェクトに Web API 関係の NuGet パッケージをインストールします。必要なパッケージは、Web API テンプレートで自動生成されたプロジェクトの NuGet パッケージの管理画面で「インストール済み」の WebApi 関係のパッケージを表示すると分かります。自分の環境では以下の 6 つでした。

  • Microsoft.AspNet.WebApi
  • Microsoft.AspNet.WebApi.Client
  • Microsoft.AspNet.WebApi.Core
  • Microsoft.AspNet.WebApi.WebHost
  • Microsoft.AspNet.WebApi.Owin
  • Microsoft.AspNet.WebApi.HelpPage

Microsoft.AspNet.WebApi を選んでインストールすると自動的に Client, Core, WebHost もインストールされます。Owin, HelpPage はその後で追加インストールしました。

(3) RequireHttpsAttribute の追加

SSL 通信を強制するためのフィルター RequireHttpsAttribute を追加します。(基本的な動作には影響ないのですが、実装しておいた方がよさそうですので)

まず、MVC 側ですが、MVC 5.2 以降であれば System.Web.Mvc 名前空間に RequireHttpsAttribute Class が用意されていますので、それを FilterConfig.cs で以下のように追加します。

public static void RegisterGlobalFilters(
                GlobalFilterCollection filters)
{
    filters.Add(new HandleErrorAttribute());

    // これを追加
    filters.Add(new RequireHttpsAttribute());
}

Web API 側では上記のフィルターは使えないので、カスタムフィルターを作ってそれを使うことになります。(MVC 用と Web API 用とではフィルターは違うので注意)

カスタムフィルターは、Microsoft の文書 Secure a Web API with Individual Accounts and Local Login in ASP.NET Web API 2.2 からリンクが張ってある GitHub のページ からダウンロードできるプロジェクトの Filters フォルダにサンプルがありましたので、それを借用しました。

そのカスタムフィルター RequireHttpsAttribute.cs のコードをそのまま載せておきます。(名前空間は自分のプロジェクトに合わせて変更しました)

using System;
using System.Net;
using System.Net.Http;
using System.Web.Http.Controllers;
using System.Web.Http.Filters;

namespace Mvc5.Filters
{
  public class RequireHttpsAttribute : 
                            AuthorizationFilterAttribute
  {
    public int Port { get; set; }

    public RequireHttpsAttribute()
    {
      Port = 443;
    }

    public override void OnAuthorization(
                           HttpActionContext actionContext)
    {
      var request = actionContext.Request;

      if (request.RequestUri.Scheme != Uri.UriSchemeHttps)
      {
        var response = new HttpResponseMessage();

        if (request.Method == HttpMethod.Get || 
            request.Method == HttpMethod.Head)
        {
          var uri = new UriBuilder(request.RequestUri);
          uri.Scheme = Uri.UriSchemeHttps;
          uri.Port = this.Port;

          response.StatusCode = HttpStatusCode.Found;
          response.Headers.Location = uri.Uri;
        }
        else
        {
          response.StatusCode = HttpStatusCode.Forbidden;
        }

        actionContext.Response = response;
      }
      else
      {
        base.OnAuthorization(actionContext);
      }
    }
  }
}

このカスタムフィルターを有効にする方法は下の「(4) WebConfig.cs の追加」のセクションを見てください。

(4) WebConfig.cs の追加

Web API プロジェクトから WebConfig.cs をコピーして MVC プロジェクトの App_Start フォルダにコピーします。名前空間は自分のプロジェクトに合わせて変更してください。

Register メソッドに、上の「(3) RequireHttpsAttribute の追加」のセクションで用意した Web API 用のカスタムフィルターを有効化するため、以下のコードを追加します。

public static void Register(HttpConfiguration config)
{
  // ・・・中略・・・

  // カスタム RequireHttpsAttribute フィルターを追加
  config.Filters.Add(new Mvc5.Filters.RequireHttpsAttribute());
}

その後、WebConfig を Global.asax の Application_Start メソッドに登録します。

protected void Application_Start()
{
    // ・・・中略・・・

    // これを追加
    GlobalConfiguration.Configure(WebApiConfig.Register);
}

(5) UseOAuthBearerTokens 追加

トークン認証を有効にするため、Startup.Auth.cs の ConfigureAuth メソッドに以下のコードを追加します。コードは Web API テンプレートで作成したプロジェクトにありますので、それをコピーして修正すればいいです。

public partial class Startup
{
  // 追加
  public static OAuthAuthorizationServerOptions 
                          OAuthOptions { get; private set; }

  // 追加
  public static string PublicClientId { get; private set; }

  public void ConfigureAuth(IAppBuilder app)
  {

    // ・・・中略・・・

    // 追加
    PublicClientId = "self";
    OAuthOptions = new OAuthAuthorizationServerOptions
    {
      TokenEndpointPath = new PathString("/Token"),
      Provider = new ApplicationOAuthProvider(PublicClientId),

      // 不要と思われるのでコメントアウト(説明下記)
      //AuthorizeEndpointPath = 
      //        new PathString("/api/Account/ExternalLogin"),

      // デフォルトで 20 分。MVC 側に合わせて 14 日に設定
      AccessTokenExpireTimeSpan = TimeSpan.FromDays(14),

      // SSL 強制のため false に設定
      AllowInsecureHttp = false
    };
    app.UseOAuthBearerTokens(OAuthOptions);

    // ・・・後略・・・

AllowInsecureHttp プロパティは SSL 通信強制のため false に設定してください。

ApplicationOAuthProvider は Web API テンプレートで作成したプロジェクトの Providers フォルダにあるものをコピーして使います。詳しくは下の「(6) ApplicationOAuthProvider 追加」セクションを見てください。

AuthorizeEndpointPath プロパティについては、stackoverflow の記事 What is AuthorizeEndpointPath? に説明があります。

ユーザーがクレデンシャルを入力してトークンを得るという条件に限定すれば、AuthorizeEndpointPath プロパティの設定はコメントアウトしても問題なさそうです。(それで 100% 問題ないと言い切れる自信はないですが)

(6) ApplicationOAuthProvider 追加

Web API テンプレートで作成したプロジェクトの Providers フォルダにある ApplicationOAuthProvider.cs をフォルダごと MVC プロジェクトにコピーします。名前空間は自分のプロジェクトに合わせて変更してください。

GrantResourceOwnerCredentials メソッドの中で使用されている第 2 引数に string を持つ GenerateUserIdentityAsync メソッドは、Web API プロジェクトの IdentityModel.cs に定義されているものをコピーして、MVC プロジェクトの IdentityConfig.cs にペーストしてください。

このプロバイダは、OWIN ミドルウェアのプラグインとして、OWIN ミドルウェアで発生するイベントを処理するためのものだそうです。詳しくは Microsoft の文書 Secure a Web API with Individual Accounts and Local Login in ASP.NET Web API 2.2 の Configuring the Authorization Server のセクションを見てください。

(7) トークン取得・削除のスクリプト

トークン取得・削除のスクリプトは以下のコードのようにします。

var tokenKey = 'accessToken';

function getToken() {
    var email = document.getElementById("email").value;
    var password = document.getElementById("password").value;

    var loginData = {
        grant_type: 'password',
        username: email,
        password: password
    };

    $.ajax({
        type: "POST",
        url: "/Token",
        data: loginData,
        success: function (data) {
            sessionStorage.setItem(tokenKey, data.access_token);
        },
        error: function (jqXHR, textStatus, errorThrown) {
            //・・・中略・・・
        }
    });
}

function removeToken() {
    sessionStorage.removeItem(tokenKey);
}

以上で MVC 側はクッキーベースで、Web API 側はトークンベースで独立して認証が働きます。それを可能にしているのは WebConfig.cs に含まれている以下の 2 行です。

config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(
                    OAuthDefaults.AuthenticationType));

この 2 行をコメントアウトすると Web API 側もクッキーベースの認証となります。なお、その際 Web API に匿名アクセスすると 401 応答ではなく、以下のように 200 応答となりますので注意してください。

クッキー認証の場合の 200 応答

Tags: ,

MVC

ASP.NET Web API の認証

by WebSurfer 2018年12月17日 15:40

Visual Studio 2013 / 2015 の Web API テンプレートで、認証を「個別のユーザーアカウントカウント」として自動生成させた Web API アプリの認証は、デフォルトで、トークンを要求ヘッダに含めてサーバーに送信するトークンベースになります。(MVC テンプレートは不明。Core は全く不明)

ベアラトークン

上の画像は Web API に要求をかけたときの要求ヘッダを Fiddler で表示したものです。赤枠で示した部分が認証に使われるトークンです。(画像には認証クッキーも含まれていますが、その理由等については後述します)

(注:クッキーではなくトークンを使う理由はセキュリティ (CSRF) 対策だそうです。MVC アプリではビューに Html ヘルパーの AntiForgeryToken メソッドを、アクションメソッドに [ValidateAntiForgeryToken] 属性を付与して CSRF 対策ができますが、Web API ではそういう手段が使えませんから)

テンプレートで自動生成されたそのままの状態でアプリを実行してトークンが送られるわけではないです。トークンを送って認証が通るようにする手順と注意事項を以下に書いておきます。

トークンによる認証の仕組みについては Microsoft の文書 Secure a Web API with Individual Accounts and Local Login in ASP.NET Web API 2.2 に詳しく書いてありますので、それを読むことをお勧めします。

その記事からリンクが張ってある GitHub のサイトから完全なサンプルが入手できます。それを試してもらってもいいのですが、そのサンプルには SSL 関係や knockout.js など Web API の認証とは直接関係のない部分もあって分かり難いところがあるかもしれません。(注:運用上は SSL の実装は必須です。knockout.js は必須ではありませんが)

なので、以下に Visual Studio 2015 の Web API テンプレート作成したアプリに、追加で何を実装すればトークンを入手でき、そしてトークンをどのように送信すれば認証が通るかだけを書いておきます。

事前に、ASP.NET Identity がユーザー情報を取得する LocalDB に email と password を登録し有効なユーザーアカウントを作成しておく必要があります。(その手順は本題とは直接関係ないので割愛します。手抜きでスミマセン)

トークンの入手は、ビューに以下のようなスクリプトを追加し、jQuery ajax を使って登録済みの email と password をトークンエンドポイント /Token に POST してやります。email と password が有効であれば、応答の JSON 文字列にトークンが含まれて戻ってきます。そのトークンを sessionStorage に保存します。

var tokenKey = 'accessToken';

function getToken() {
    var email = document.getElementById("email").value;
    var password = document.getElementById("password").value;

    var loginData = {
        grant_type: 'password',
        username: email,
        password: password
    };

    $.ajax({
        type: "POST",
        url: "/Token",
        data: loginData,
        success: function (data) {
          sessionStorage.setItem(tokenKey, data.access_token);
        },
        error: function (jqXHR, textStatus, errorThrown) {
            // ・・・中略・・・
        }
    });
}

トークンの送信は、上の getToken メソッドで sessionStorage に保存したトークンを取得し、以下のように要求ヘッダの Authorization に設定して送信してやります。それで認証が通って期待した応答が返ってきます。

function apiHeroesGet() {
    var token = sessionStorage.getItem(tokenKey);
    var headers = {};
    if (token) {
        headers.Authorization = 'Bearer ' + token;
    }

    $.ajax({
        type: "GET",
        url: "/api/values",
        headers: headers,
        success: function (data, textStatus, jqXHR) {
            // ・・・中略・・・
        },
        error: function (jqXHR, textStatus, errorThrown) {
            // ・・・中略・・・
        }
    });
}

主な話は以上ですが、追加で 2, 3 気になったことを書いておきます。

(1) トークンエンドポイン�� /Token

上のスクリプトの getToken メソッドの email と password の送信先 /Token は、Startup.Auth.cs の ConfigureAuth メソッドで OAuthAuthorizationServerOptions クラスを初期化するときに TokenEndpointPath プロパティに設定されます。

自動生成されたコードの中のアクションメソッドだと思って探したけれど、見つからないので焦ったというのは内緒です。(笑)

(2) 認証クッキーの発行

上の画像で認証クッキーも発行されていますが、それは上のスクリプトの getToken メソッドでトークンを発行する際に同時に発行されます。

デフォルトで、Startup.Auth.cs の ConfigureAuth メソッドには app.UseCookieAuthentication(...) メソッドが含まれていることに注意してください。

MVC 側のアクションメソッドの認証には、この認証クッキーが使用されます。例えば MVC 側のアクションメソッドに [Authorize] を付与してやると、認証クッキーなしでは 401 応答が返ってきます。

401 応答ではなく、302 応答を返してログインページにリダイレクトするには、引数の CookieAuthenticationOptions オブジェクトの LoginPath プロパティにログインページの URL を new PathString("/Account/Login") というように指定してやります。

(3) AuthenticationType の設定

UseCookieAuthentication メソッドの引数 CookieAuthenticationOptions オブジェクトの AuthenticationType を設定すると、トークンエンドポイント /Token に email と password を送信しても認証クッキーは発行されなくなります。(なお、トークンは発行されます)

認証クッキーは MVC 側でのログインだけで発行したい場合は、AuthenticationType を設定すればよさそうです。そのことが書いてあるドキュメントは見つけられなかったのですが、そのような目的のためにそうなっているような気がします。

AuthenticationType に設定されるのは単なる文字列で、何かの識別用に使っているらしいです。これをきちんと指定しないとログアウトできないなどの問題が出るという stackoverflow の記事を見かけました。(自分が Web API テンプレートのアプリで試した限りではそういう問題は出なかったですが・・・)

(4) トークンによる認証の強制

デフォルトでは Web API 側の認証はトークンになるのは、WebApiConfig.cs にある以下のコードによります。

config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(
                OAuthDefaults.AuthenticationType));

前者のメソッドでホスト(IIS と MVC) による認証は無視され、Web API には匿名アクセスとなるらしいです。後者のメソッドで HostAuthenticationFilter によりベアラトークンによる認証が行われるようになるそうです。

ちなみに、上の 2 行をコメントアウトすると認証クッキーだけで認証が通ってしまうようになります。

詳しくは、Microsoft の文書 Secure a Web API with Individual Accounts and Local Login in ASP.NET Web API 2.2 の Configuring Web API to use Bearer Tokens のセクションに詳しく書いてありますので、そちらを見てください。

Tags: ,

MVC

About this blog

2010年5月にこのブログを立ち上げました。その後 ブログ2 を追加し、ここは ASP.NET 関係のトピックス、ブログ2はそれ以外のトピックスに分けました。

Calendar

<<  2019年2月  >>
272829303112
3456789
10111213141516
17181920212223
242526272812
3456789

View posts in large calendar