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

MVC5 で AjaxHelper が働かない

by WebSurfer 2018年5月28日 15:40

Visual Studio Community 2015(以下、VS2015 と書きます)のテンプレートで [MVC] を選択して生成した ASP.NET MVC5 アプリケーションでは、そのままでは AjaxHelper(Ajax.BeginForm, Ajax.ActionLink など)が働きません。その理由と解決策を書きます。

Ajax.ActionLink を利用したページ

上の画像は Ajax.ActionLink を使って a 要素の href 属性に部分ビューの url を設定したハイパーリンクをレンダリングし(青文字の部分)、ユーザーがハイパーリンクをクリックすると Ajax を利用して部分ビューを呼び出し、応答を UpdateTargetId に指定した div 要素内に書き出した結果です(Customer Details 以下の部分)。

上の画像のアプリは対策済みなので、Ajax を利用した部分ビューの呼び出しと、UpdateTargetId に指定した div 要素内へ応答の書き出しは成功しています。

未対策の場合はハイパーリンクは普通の a 要素の機能しかないので、クリックすると部分ビューは同期呼び出しされ、ブラウザの現在のウィンドウ全体が部分ビューに書き換えられて表示されます。

なぜそうなるかと言うと、必要な外部 .js ファイルがダウンロードされる設定がされてないからです。

そんな設定は自分でやるのが当たり前と言われるかもしれませんが、Visual Studio 2010 Professional(以下、VS2010 と書きます)のテンプレートで自動生成される MVC4 アプリのプロジェクトでは、AjaxHelper に必要な外部 .js ファイルがダウンロードされる設定が含まれているのです。

なので、VS2015 で MVC5 アプリのプロジェクトを作って、既存の MVC4 の AjaxHelper を使うコードを MVC5 に移植しただけでは期待した Ajax の機能は働きません。

何を隠そう、自分はその理由が分からずハマってしまいました。(汗) また無駄な時間を費やすことがないよう、以下に備忘録として原因と対策を書いておきます。

MVC4 以降は控えめな JavaScript を利用して、Ajax.BeginForm, Ajax.ActionLink などから生成される form 要素、a 要素の属性の設定に応じて Ajax 呼び出しと応答の表示を行うようになっています。

その控えめな JavaScript は jquery.unobtrusive-ajax.js または jquery.unobtrusive-ajax.min.js というファイルに含まれています。(ちなみに、MVC3 以前は「控えめ」ではない MicrosoftAjax.js と MicrosoftMvcAjax.js を使います)

例えば、Ajax.ActionLink を使った場合、上の画像の例の「Ms. Rosmarie Carroll」のハイパーリンクの html 要素は以下のようになります。

<a  data-ajax="true" 
    data-ajax-mode="replace" 
    data-ajax-update="#results" 
    href="/Customer/Details/6">
    Ms. Rosmarie Carroll
</a>

控えめな JavaScript は、上の a 要素の属性 data-ajax, data-ajax-mode, data-ajax-update の設定に従って Ajax 呼び出しと応答の処理を行います。

従って、やるべきことは控えめな JavaScript の外部ファイルがダウンロードされるように設定を行うことです。VS2010 で作る MVC4 アプリはそのあたりの設定が自動的に行われますが、VS2015 で作る MVC5 アプリは開発者が自分で設定しなければなりません。

前置きが長くなりましたが、その方法を以下に書きます。

まず、NuGet から Microsoft.jQuery.Unobtrusive.Ajax をインストールします。以下の画像を見てください。

NuGet からインストール

インストールが完了すると、Script フォルダに jquery.unobtrusive-ajax.js, jquery.unobtrusive-ajax.min.js がインストールされるはずですのでチェックしてください。

スクリプトファイル

App_Start フォルダの BundleConfig.cs ファイルを開いて上記 .js ファイルがバンドルされるように設定します。下の画像の赤枠部分を見てください。ここでは MVC4 と同様に、クライアント側での検証用のスクリプトファイルと一緒にダウンロードされるようにしています。

BundleConfig.cs の設定

_Layout.cshtml には @RenderSection("scripts", required: false) が含まれているので、_Layout.cshtml を使う View では以下のコードを追加すれば OK です。

@section Scripts {
    @Scripts.Render("~/bundles/jqueryval")
}

_Layout.cshtml を使わない View の場合は @Scripts.Render("~/bundles/jqueryval") の位置が jQuery.js の後に来るように注意してください。

Tags:

MVC

MVC で Chart を利用する方法

by WebSurfer 2018年5月23日 16:51

ASP.NET Web Forms アプリ用のサーバーコントロールである Chart を MVC アプリで利用する方法を書きます。

MVC アプリで Chart 利用

一般的に、Web Forms 用のサーバーコントロールは、MVC でも ViewState とポストバックを使わない範囲であれば利用できます。

Chart の場合、要求を受けてグラフの画像ファイルを生成するという操作には ViewState とポストバックは不要ですので、MVC でも利用できます。

問題は Chart が生成した画像ファイルを、どのようにブラウザが取得して表示できるかということです。

ASP.NET Web Forms アプリの場合、Chart が生成した画像ファイルはサーバーの c:\TempImageFiles フォルダに一時保存されます。そして、.aspx ページ上で Chart を配置した位置に img 要素がレンダリングされ、その src 属性に画像ファイルを取得するための HTTP ハンドラ設定されます。

ブラウザがページを受信すると、ブラウザは img 要素の src 属性に設定された HTTP ハンドラをサーバーに要求します。要求を受けたサーバーは HTTP ハンドラを使って一時保存された画像ファイルを取得してブラウザに送信し、ブラウザは受信した画像を img 要素の位置に表示するという仕組みになっています。詳しくは、先の記事「Chart (ASP.NET Web Forms 用)」を見てください。

MVC アプリでも .aspx 形式の View を使う場合は Web Forms アプリと同等なことができると思いますが(未検証・未確認です)、最近の主流らしい Razor 形式の View の場合は img 要素のレンダリングが問題です。

ではどうするかと言うと、Web Forms アプリのような HTTP ハンドラは利用せず、Controller のアクションメソッドで画像をダウンロードするようにし、img 要素は自分で View に書いて、その src 属性にアクションメソッドの url を設定してやります。

アクションメソッドでは、動的に Chart を生成し、Chart.SaveImage メソッドを使って画像データを MemoryStream に保存し、Controller.File メソッド (Byte[], String) で画像のバイト列を応答ストリームに書き出すようにします。

先の記事「Chart」と同じ SQL Server のデータから、同じグラフ画像を生成してダウンロードさせるアクションメソッドのサンプルを以下に書いておきます。このアクションメソッドの url を img 要素の src 属性に設定した結果が上の画像です。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using AdventureWorksLT;
using Mvc5App.Extensions;
using System.ComponentModel.DataAnnotations;

// Chart を利用するコードのため以下を追加する
using System.Web.UI.DataVisualization.Charting;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.IO;

namespace Mvc5App.Controllers
{
  public class HomeController : Controller
  {        
    public ActionResult Chart()
    {
      SqlDataSource sds = new SqlDataSource()
      {
        ID = "SqlDataSource1",
        ConnectionString = @"接続文字列",
        SelectCommand = @"SELECT 
        Month, 
        SUM(CASE WHEN Name=N'三吉' THEN Sales ELSE 0 END) AS 三吉, 
        SUM(CASE WHEN Name=N'春日' THEN Sales ELSE 0 END) AS 春日, 
        SUM(CASE WHEN Name=N'東雲' THEN Sales ELSE 0 END) AS 東雲, 
        SUM(CASE WHEN Name=N'府中' THEN Sales ELSE 0 END) AS 府中, 
        SUM(CASE WHEN Name=N'広島' THEN Sales ELSE 0 END) AS 広島 
        FROM Shop GROUP BY Month"
      };

      Chart chart = new Chart()
      {
        ID = "Chart1",
        Width = 600,
        DataSource = sds.Select(DataSourceSelectArguments.Empty)
                
        // 注:以下のようにするとエラー
        // DataSourceID = "SqlDataSource1"
      };

      Legend legend = new Legend()
      {
        DockedToChartArea = "ChartArea1",
        IsDockedInsideChartArea = false,
        Name = "Legend1"
      };
      chart.Legends.Add(legend);

      Series series = new Series()
      {
        Name = "三吉",
        ChartType = SeriesChartType.StackedColumn,
        CustomProperties = "DrawingStyle=Cylinder",
        IsValueShownAsLabel = true,
        Label = "#PERCENT{P1}",
        Legend = "Legend1",
        XValueMember = "Month",
        YValueMembers = "三吉"
      };
      chart.Series.Add(series);

      series = new Series()
      {
        Name = "春日",
        ChartType = SeriesChartType.StackedColumn,
        CustomProperties = "DrawingStyle=Cylinder",
        IsValueShownAsLabel = true,
        Label = "#PERCENT{P1}",
        Legend = "Legend1",
        XValueMember = "Month",
        YValueMembers = "春日"
      };
      chart.Series.Add(series);

      series = new Series()
      {
        Name = "東雲",
        ChartType = SeriesChartType.StackedColumn,
        CustomProperties = "DrawingStyle=Cylinder",
        IsValueShownAsLabel = true,
        Label = "#PERCENT{P1}",
        Legend = "Legend1",
        XValueMember = "Month",
        YValueMembers = "東雲"
      };
      chart.Series.Add(series);

      series = new Series()
      {
        Name = "府中",
        ChartType = SeriesChartType.StackedColumn,
        CustomProperties = "DrawingStyle=Cylinder",
        IsValueShownAsLabel = true,
        Label = "#PERCENT{P1}",
        Legend = "Legend1",
        XValueMember = "Month",
        YValueMembers = "府中"
      };
      chart.Series.Add(series);

      series = new Series()
      {
        Name = "広島",
        ChartType = SeriesChartType.StackedColumn,
        CustomProperties = "DrawingStyle=Cylinder",
        IsValueShownAsLabel = true,
        Label = "#PERCENT{P1}",
        Legend = "Legend1",
        XValueMember = "Month",
        YValueMembers = "広島"
      };
      chart.Series.Add(series);

      ChartArea chartArea = new ChartArea()
      {
        Name = "ChartArea1",
        AxisY = new Axis() { Title = "売上高" },
        AxisX = new Axis() { Title = "売上月" }
      };
      chart.ChartAreas.Add(chartArea);

      using (var ms = new MemoryStream())
      {
        chart.SaveImage(ms, ChartImageFormat.Png);

        // キャッシュを許可するか否か、許可する場合は有効期限を
        // 指定しておくべき。
        // 以下のコードはキャッシュを許可しない場合の例。応答ヘ
        // ッダーは次のようになる。
        //    Cache-Control: no-cache
        //    Pragma: no-cache
        //    Expires: -1
        Response.Cache.SetCacheability(HttpCacheability.NoCache);
        Response.Cache.SetExpires(DateTime.Now.ToUniversalTime());
        Response.Cache.SetMaxAge(new TimeSpan(0, 0, 0, 0));

        return File(ms.ToArray(), "image/png");

        // File の第 3 引数を以下のように設定すると応答ヘッダに
        // Content-Disposition: attachment; filename=chart.png
        // が含まれるようになる。
        // return File(ms.ToArray(), "image/png", "chart.png");
      }
    }
  }
}

先の記事「Chart (ASP.NET Web Forms 用)」で書きました Web Forms アプリでは、Visual Studio によって自動的に web.config に HTTP ハンドラの定義や appSettings の設定が追加されますが、それらは一切不要です。

ただし、System.Web.DataVisualization の参照への追加が必要です。using 句を追加するだけではダメです。

参照の追加

Tags:

MVC

About this blog

2010年5月にこのブログを立ち上げました。主に ASP.NET Web アプリ関係の記事です。

Calendar

<<  2024年4月  >>
31123456
78910111213
14151617181920
21222324252627
2829301234
567891011

View posts in large calendar