WebSurfer's Home

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

絵文字の入力制限

by WebSurfer 2010年10月19日 21:02

MSDN フォーラムで、名前などの入力項目に携帯の絵文字を入力させないように、以下のステップで対応したいという話がありました。

  1. ユーザーのキャリアを判定
  2. キャリア別に入力の絵文字をチェック
  3. 絵文字が含まれていればエラー表示

フォーラムでは、このうち 2 番目のステップについて議論があり、自分が提案した方法でうまくいったそうですので、その内容を書いておきます。

ASP.NET の Web アプリのサーバー側のプログラムでは文字列は Unicode で扱われるので、絵文字に該当する Unicode 文字が分かれば、正規表現を使ってチェックできるはずです。

Wikipedia のページ 携帯電話の絵文字 によると、各キャリアの絵文字の Unicode 表現は以下のとおりとのことです。

  • au: E468-E5DF, EA80-EB88
  • DoCoMo: E63E-E6A5, E6AC-E6AE, E6B1-E6BA, E6CE-E757
  • SoftBank: E001-E05A, E101-E15A, E201-E253, E301-E34D, E401-E44C, E501-E537
  • emobile: E63E-E6A5, E6AC-E6AE, E6B1-E6BA, E6CE-E757, E600-E619

ユーザーが TextBox などに入力して POST した文字列をサーバー側で取得したとき、絵文字は上記の Unicode 文字になるはずです。

キャリア別に処置する必要がなければ、ちょっと乱暴かもしれませんが、外字全部(U+E000〜U+F8FF)を対象としてもいいかもしれません。

i絵文字

i-mode 絵文字入りの Shift_JIS コードを String 型の文字列 (Unicode) に変換し、正規表現を使って絵文字の有無のチェックおよび除去するサンプルを、C# のコンソールアプリで作って検証してみました。

ドコモが提供している絵文字入力ソフト「i絵文字」で、テキストファイルに簡単に i-mode 絵文字を挿入して試験できるのでお試しください。

全部の i-mode 絵文字を試したわけではありませんが、自分が試した限りでは i-mode 絵文字の有無のチェックおよび除去は以下のサンプルで可能でした。

au, Softbank, emobile も、上に紹介した「携帯電話の絵文字」のページの Unicode で pattern を作れば、チェックや除去は可能だと思います。

なお、ドコモ以外は検証していませんし、保証はできませんので、参考にするならご自分で十分検証してくださいね。

using System;
using System.Collections.Generic;
using System.Text;
using System.IO;
using System.Text.RegularExpressions;

namespace EmojiCheck
{
  class Program
  {
    static void Main(string[] args)
    {
      // testfile.txt は試験用の Shift_JIS のテキストファイル。

      string path = "testfile.txt";

      if (File.Exists(path))
      {
        string text = string.Empty;

        using (FileStream fs = File.Open(path, FileMode.Open))
        {
          byte[] b = new byte[(int)fs.Length];
          Encoding enc = Encoding.GetEncoding("shift_jis");
          fs.Read(b, 0, b.Length);
          text = enc.GetString(b);
        }

        Console.WriteLine(text);
        Console.WriteLine("----------------------------");

        // i-mode 絵文字の Unicode 表現
        string pattern = 
          @"[\uE63E-\uE6A5]|[\uE6AC-\uE6AE]|[\uE6B1-\uE6BA]|[\uE6CE-\uE757]";
        if (Regex.IsMatch(text, pattern))
        {
          Console.WriteLine("i-mode 絵文字があります。");
        }
        else
        {
          Console.WriteLine("i-mode 絵文字はありません。");
        }
        Console.WriteLine("----------------------------");

        // 絵文字を除去した文字列を取得
        Regex rgx = new Regex(pattern);
        string outputStr = rgx.Replace(text, "");
        Console.WriteLine(outputStr);
      }
    }
  }
}

Tags:

ASP.NET

ModalPopup で編集・更新操作

by WebSurfer 2010年10月14日 23:18

DB の更新などで、まず一覧画面にレコード一覧を表示してユーザーに選択させ、一覧画面は開いたまま別に編集画面を開いてそこでレコードを編集してもらい、編集画面の[更新]ボタンクリックで DB を更新すると共に編集画面は閉じて、更新結果を一覧画面に反映したいといった要求を時々聞きます。

1 ページで行うか、2 ページ使う場合は一覧画面 → 編集画面 → 一覧画面と遷移していけば難しくありませんが、上記のように開いたままの一覧画面に、編集画面による更新結果を反映するのは難しいです(というよりほとんど無理と思います)。

という訳で、実際は 1 ページで行って、見かけは 2 画面で行うようにする方法を紹介します。

2 画面で選択・編集・更新操作

具体的にどうするかというと、一覧を表示する GridView と、レコードの編集を行う DetailsView を 1 ページに配置し、DetailsView を ASP.NET AJAX Control Toolkit の ModalPopup で表示するというものです。

GridView の各行に配置した[編集]ボタンをクリックすると、ModalPopup の中の DetailsView が表示され、当該行のレコードを編集できます。その間、バックグラウンドは暗く表示され、GridView の操作はできません。

DetailsView の編集が終わったら、[更新]ボタン(上の画像、下のコードでは[Save]ボタン)をクリックすると DB の当該レコードが更新され、ModalPopup が非表示になります。

そして GridView 上で更新された行がハイライトされます。(以下のコードでは、バックグラウンドが黄色に変わり、6 秒間で3 回点滅した後、元のスタイルに戻ります)。

コードは以下のとおりです。Microsoft 提供のサンプルデータベース Northwind の Customers テーブルを使用しています。

なお、Opera 10.63 では[Save]ボタンをクリックしたとき ModalPopup が消えないという問題があります。IE8, Firefox 3.6.10, Safari 5.0.2 は問題なしでした。

<%@ Page Language="C#" %>
<%@ Register Assembly="AjaxControlToolkit"
  Namespace="AjaxControlToolkit" 
  TagPrefix="asp" %>

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">

<script runat="server">

  protected void GridView1_SelectedIndexChanged(object sender, EventArgs e)
  {
    DetailsView1.DefaultMode = DetailsViewMode.Edit;
    UpdatePanel2.Update();
    ModalPopupExtender1.Show();
  }

  protected void  LinkButton1_Click(object sender, EventArgs e)
  {
    DetailsView1.UpdateItem(false);
    GridView1.DataBind();
    UpdatePanel1.Update();
    ModalPopupExtender1.Hide();

    if (ScriptManager.GetCurrent(this).IsInAsyncPostBack)
    {
      ScriptManager.GetCurrent(this).RegisterDataItem(GridView1, 
        GridView1.SelectedIndex.ToString());
    }
  }
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title></title>
  <script src="Scripts/jquery-1.4.1.js" type="text/javascript"></script>
  <style type="text/css">
    /*Modal Popup*/
    .modalBackground {
      background-color: Gray;
      filter: alpha(opacity=70);
      opacity: 0.7;
    }

    tr.updated td {
      background-color: yellow;
    }

    .detail {
      background-color: #ffffff;
    }
  </style>
</head>
<body>
  <form id="form1" runat="server">
  <asp:ToolkitScriptManager ID="ToolkitScriptManager1" runat="server">
  </asp:ToolkitScriptManager>
  <script type="text/javascript">
  <!--
    Sys.WebForms.PageRequestManager.getInstance().add_pageLoaded(pageLoaded);

    function pageLoaded(sender, args) {
      var updateRowIndex = args.get_dataItems()["GridView1"];
      if (updateRowIndex) {
        var tr = $get("GridView1").rows[parseInt(updateRowIndex) + 1];
        $(tr).addClass('updated').children('td')
          .fadeTo(1000, 0.33).fadeTo(1000, 1.0)
          .fadeTo(1000, 0.33).fadeTo(1000, 1.0)
          .fadeTo(1000, 0.33).fadeTo(1000, 1.0);
        window.setTimeout(function () {
          $(tr).removeClass('updated');
        }, 6000);
      }
    }
  //-->
  </script>
  <div>
    <asp:SqlDataSource ID="SqlDataSource1" runat="server" 
      ConnectionString="<%$ ConnectionStrings:Northwind %>" 
      SelectCommand=
        "SELECT [CustomerID], [CompanyName], [ContactName], 
          [ContactTitle], [Phone] 
        FROM [Customers]">
    </asp:SqlDataSource>
    <asp:UpdatePanel ID="UpdatePanel1" 
      runat="server" 
      UpdateMode="Conditional">
      <ContentTemplate>
        <asp:GridView ID="GridView1" 
          runat="server" 
          AutoGenerateColumns="False" 
          DataKeyNames="CustomerID" 
          DataSourceID="SqlDataSource1" 
          EnableModelValidation="True" 
          OnSelectedIndexChanged="GridView1_SelectedIndexChanged" 
          AllowPaging="True">
          <Columns>
            <asp:BoundField DataField="CustomerID" 
              HeaderText="CustomerID" 
              ReadOnly="True" 
              SortExpression="CustomerID" />
            <asp:BoundField DataField="CompanyName" 
              HeaderText="CompanyName" 
              SortExpression="CompanyName" />
            <asp:BoundField DataField="ContactName" 
              HeaderText="ContactName" 
              SortExpression="ContactName" />
            <asp:BoundField DataField="ContactTitle" 
              HeaderText="ContactTitle" 
              SortExpression="ContactTitle" />
            <asp:BoundField DataField="Phone" 
              HeaderText="Phone" 
              SortExpression="Phone" />
            <asp:CommandField SelectText="編集" 
              ShowSelectButton="True" />
          </Columns>
        </asp:GridView>
      </ContentTemplate>
    </asp:UpdatePanel>
  </div>
  <div>
    <asp:SqlDataSource ID="SqlDataSource2" runat="server" 
      ConnectionString="<%$ ConnectionStrings:Northwind %>" 
      SelectCommand=
        "SELECT [CustomerID], [CompanyName], [ContactName], 
           [ContactTitle], [Phone] 
        FROM [Customers] 
        WHERE ([CustomerID] = @CustomerID)" 
      UpdateCommand=
        "UPDATE [Customers] 
        SET [CompanyName]=@CompanyName, [ContactName]=@ContactName, 
          [ContactTitle]=@ContactTitle, [Phone]=@Phone 
        WHERE [CustomerID] = @CustomerID">
      <SelectParameters>
        <asp:ControlParameter ControlID="GridView1" Name="CustomerID" 
          PropertyName="SelectedValue" Type="String" />
      </SelectParameters>
      <UpdateParameters>
        <asp:Parameter Name="CompanyName" Type="String" />
        <asp:Parameter Name="ContactName" Type="String" />
        <asp:Parameter Name="ContactTitle" Type="String" />
        <asp:Parameter Name="Phone" Type="String" />
        <asp:Parameter Name="CustomerID" Type="String" />
      </UpdateParameters>
    </asp:SqlDataSource>
    <asp:Panel ID="Panel1" runat="server" CssClass="detail">
      <asp:UpdatePanel ID="UpdatePanel2" 
        runat="server" 
        UpdateMode="Conditional">
        <ContentTemplate>
          <asp:Button ID="DummyButton" 
            runat="server" 
            style="display: none;" />
          <asp:DetailsView ID="DetailsView1" 
            runat="server" 
            AutoGenerateRows="False" 
            DataKeyNames="CustomerID" 
            DataSourceID="SqlDataSource2" 
            EnableModelValidation="True">
            <Fields>
              <asp:BoundField DataField="CustomerID" 
                HeaderText="CustomerID" 
                ReadOnly="True" 
                SortExpression="CustomerID" />
              <asp:BoundField DataField="CompanyName" 
                HeaderText="CompanyName" 
                SortExpression="CompanyName" />
              <asp:BoundField DataField="ContactName" 
                HeaderText="ContactName" 
                SortExpression="ContactName" />
              <asp:BoundField DataField="ContactTitle" 
                HeaderText="ContactTitle" 
                SortExpression="ContactTitle" />
              <asp:BoundField DataField="Phone" 
                HeaderText="Phone" 
                SortExpression="Phone" />
            </Fields>
          </asp:DetailsView>
          <div>
            <asp:LinkButton ID="LinkButton1" 
              runat="server" 
              onclick="LinkButton1_Click" 
              Text="Save" />
            <asp:LinkButton ID="LinkButton2" 
              runat="server" 
              CausesValidation="False" 
              Text="Cancel" />
          </div>
          <asp:ModalPopupExtender ID="ModalPopupExtender1" 
            runat="server" 
            PopupControlID="Panel1" 
            TargetControlID="DummyButton" 
            CancelControlID="LinkButton2" 
            BackgroundCssClass="modalBackground">
          </asp:ModalPopupExtender>
        </ContentTemplate>
      </asp:UpdatePanel> 
    </asp:Panel> 
  </div>
  </form>
</body>
</html>

ネタは先の記事 jQuery の本を買いました で紹介した本のサンプルです(サンプルそのままではなく、不要な部分の削除、誤りの修正を行っています)。

jQuery の本なのに、jQuery を使っているのは GridView 上で更新された行がハイライトされるところだけです。もっと使えるのかと思っていたのですが、更新操作のサーバーとの連携が難しいようで、ちょっと期待はずれでした。

---------------- 2011/5/29 追記 ----------------

Opera では[Save]ボタンをクリックしたとき ModalPopup が消えないという問題があると書きましたが、UpdatePanel で囲うのを GridView と DetailsView に分けず一つにまとめるとその問題が回避できます。

その場合、GridView1_SelectedIndexChanged メソッドの UpdatePanel2.Update(); および LinkButton1_Click メソッドの UpdatePanel1.Update(); は不要です。

Tags: , , ,

AJAX

customErrors と requestFiltering

by WebSurfer 2010年10月12日 12:17

先の記事 アプリケーションレベルの例外処理 で書きました、(1) Button 6 クリックおよび (2) 存在しない静的ファイルを要求した場合は、web.config の customErrors 要素にカスタムエラーページを設定してもそれが表示されない(下の画像のような標準エラーページが表示される)件につき理由を調べてみました。

標準エラーページ

調査結果をまとめると以下のとおりです(自信度 99% ぐらい。残り 1% の不安要素は後述します)。

(1) Button 6 クリック(Global-NoCatch.asax へリダイレクト)

IIS7 で導入された 要求のフィルタリング で、特定の拡張子(asa, asax, ascx など)のファイルに対する要求がブロックされています。

Global-NoCatch.asax へリダイレクトした場合は以下のようになります。拡張子でチェックしているので Global.asax でも結果は同じです。

要求フィルタモジュール (RequestFilteringModule) が拡張子 asax をチェックして拒否。 → 静的ファイルハンドラー (StaticFile) によって 404.7 エラーとして処理される。 → デフォルト(httpErrors 設定なし)では標準エラーページを返す。

先に applicationHost.config の設定を変えて(fileExtension=".asax" allowed="false" を true にして)試したときも、やはり標準エラーページになったのは、asax が静的ファイルハンドラー (StaticFile) によって処理されたためと思われます。

注意:
2014/4/19 時点の最新のサンプルでは、Button 6 クリックでのリダイレクト先が Global-NoCatch.asax から NonexistentPage-NoCatch.aspx/xxx...(xxx... は x が 500 文字)に変わっています。(遅くとも 2011/4/23 には変わっていたはず) 結果、HTTP/1.1 400 Bad Request が返ってきます。詳しくは下の「2011/4/23 追記」と「2011/8/22 追記」を見てください。

(2) 存在しない静的ファイルを要求

統合パイプラインモードでも既存のハンドラーマッピングはすべて機能したままなので、静的ファイルは依然として IIS のネイティブの静的ファイルハンドラー (StaticFile) によって処理される。 → デフォルト(httpErrors 設定なし)では標準エラーページを返す。

以上から、要求フィルタリングで拒否設定されているファイルや存在しない静的ファイルを要求されて、カスタムエラーページを返には、TechNet のページ HTTP エラー <httpErrors> の中の「カスタムエラーページを追加する方法」で述べられている手順を取るほかなさそうです。

なお、開発サーバーで検証すると動作が異なる件は、今後二度と開発サーバーを検証に利用しないことにするということで、気にしないことにしました。(笑) 開発サーバーには要求フィルタリングはなく、例外はすべて ASP.NET で処置されるということなのかもしれません(想像です)。

------ 2011/4/23 追記(2014/4/19 一部訂正) ------

上に書いたとおり、MSDN ライブラリの英語版 Complete Example for Error Handlers にコメントしましたが、それを受けてコードが一部変更されています。(日本語の方は 2011/4/23 現在以前のままですが)

具体的には、Button6 クリックでのリダイレクト先を Global-NoCatch.asax から NonexistentPage-NoCatch.aspx/xxx...(xxx... は x が 500 文字)に変えています。

でも、依然としてダメです。DefaultRedirectErrorPage.aspx にはリダイレクトされません。サーバーは HTTP/1.1 400 Bad Request を返します。


------ 2011/8/22 追記(2014/4/19 一部訂正) ------

MSDN ライブラリの英語版 Complete Example for Error Handlers に書いたコメントはすでに消されていました。また、「日本語の方は 2011/4/23 現在以前のまま」と書きましたが、英語版と同様に修正されていました。

ただし、コードの内容は 2011/4/23 時点の英語版のままです。Button 6 の説明に "Click this button to create an HTTP 400 (invalid url) error. Application_Error will catch this but will not take any action on it, and ASP.NET will redirect to DefaultRedirectErrorPage.aspx." と書いてありますが、そうはなりません。

NonexistentPage-NoCatch.aspx/xxx...(xxx... は x が 500 文字)を要求すると、サーバーは HTTP/1.1 400 Bad Request を返します。web.config の defaultRedirect に指定した DefaultRedirectErrorPage.aspx にはリダイレクトされません。

(HTTP/1.1 400 Bad Request が返ってくると、IE に表示されるのはサーバーから帰ってきた html コードではなく IE が差し替えたものになるので注意してください。Firefox の場合はサーバーから帰ってきた html コードがそのまま表示されます)

ちなみに、以前のように Global-NoCatch.asax にリダイレクトすると、「HTTP エラー 404.7 - Not Found 要求フィルタ モジュールが、ファイル拡張子を拒否するように構成されています。」という標準エラーページが返ってきます。

Tags: ,

Exception Handling

About this blog

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

Calendar

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

View posts in large calendar