WebSurfer's Home

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

リソース埋込カスタムコントロールの継承

by WebSurfer 2013年6月20日 13:15

先の記事「リソース埋め込みカスタムコントロール」で、画像、html ファイル、スクリプトファイル、css ファイルなどのリソースをアセンブリに埋め込んだカスタムコントロールの作り方を説明しました。

このようなリソース埋め込みカスタムコントロールを継承したコントロールを作って使う場合の注意点を書きます。

アセンブリに埋め込んだリソースは、GetWebResourceUrl メソッド によって URL 参照を取得して使用しますが、注意すべきはこのメソッドの第一引数 type(リソースの型)です。

先の記事の MsButton.cs のコードでは this.GetType() でリソースの型を取得していますが、このコントロールを継承した場合 this は継承先になり、ASP.NET は継承先からリソースを探すので、リソースが見つからないという結果になります。

この問題を解決するには this.GetType() に代えて typeof 演算子を利用します。先の記事の MsButton.cs のコードでは以下のようにします。

protected override void CreateChildControls()
{
  ClientScriptManager cs = Page.ClientScript;

  // このコントロールを継承する場合 this.GetType() ではダメ
  // this は継承先になり、結果、リソースが見つからない。
  //Type rsType = this.GetType();

  Type rsType = typeof(MsButton);

  // css への参照を <head></head> に配置
  HtmlLink css = new HtmlLink();
  css.Href = 
    cs.GetWebResourceUrl(rsType, "SimpleControl.MyStylesheet.css");

  // ・・・中略・・・
}

上に紹介した MSDN ライブラリのサンプルコードでも typeof 演算子を使っていますね。

Tags:

Web Custom Control

リソース埋め込みカスタムコントロール

by WebSurfer 2012年5月19日 15:26

画像、html ファイル、スクリプトファイル、css ファイルなどのリソースをアセンブリに埋め込んで使用する、カスタムコントロールのサンプルです。

リソース埋め込みカスタムコントロール

参考にしたのは、Microsoft サポートの Working with Web Resources in ASP.NET 2.0 です。サンプルコードほとんどそのままですが、CSS を参照する部分ほかを若干変更し、自分なりに注釈を加えてみました。

カスタムコントロールの本体は、table 要素の中に画像とアンカータグを配置したものです。mouseover、mouseout イベントで、コントロールの画像とラベルの色を変化させています。クリックすると、ヘルプページ Help.htm に遷移するようになっています。

必要な画像、html ファイル、スクリプトファイル、css ファイルなどのリソースは、すべてカスタムコントロールのアセンブリに埋め込んであります。

それらの内容は、以下の画像の通りです。MsButton.cs がカスタムコントロール本体、その他が埋め込んだりソースです。

Visual Studio のソリューションエクスプローラーとプロパティウィンドウ

ソースファイルの内容は以下の通りです。ソースを Visual Studio のプロジェクトに追加したら、上の画像のように、各リソースのプロパティのビルドアクションを「埋め込まれたりソース」に設定するのを忘れないようにしてください。その他、個別の注意点は下記の説明を参照してください。

実際に動かして試せるよう 実験室 にこのカスタムコントロールを使ったページをアップしましたので、興味のある方は試してみてください。


MsButton.cs

カスタムコントロールの本体です。基本は、table 要素の中に画像とアンカータグを配置したものです。

アセンブリに埋め込んだリソースは、GetWebResourceUrl メソッド によって URL 参照を取得して使用します。

2013/7/6 追記:this.GetType() ⇒ typeof(MsButton) に変更。詳細は リソース埋込カスタムコントロールの継承 を参照してください。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Web.UI.HtmlControls;

namespace SimpleControl
{
  [ToolboxData(
    "<{0}:MSButton runat='server' Text='Problems?'><{0}:MSButton>")]
  public class MsButton : WebControl
  {
    [Category("Appearance")]
    [DefaultValue("Problems?")]
    [Description("Text for Label")]
    [Localizable(true)]
    [PersistenceMode(PersistenceMode.InnerDefaultProperty)]
    public string Text
    {
      get
      {
        String s = (String)ViewState["Text"];
        return ((s == null) ? String.Empty : s);
      }
      set
      {
        ViewState["Text"] = value;
      }
    }

    protected override void CreateChildControls()
    {
      ClientScriptManager cs = Page.ClientScript;

      // 2013/7/6 修正
      // このコントロールを継承する場合 this.GetType() ではダメ
      // this は継承先になり、結果、リソースが見つからない。
      //Type rsType = this.GetType();

      Type rsType = typeof(MsButton);

      // css への参照を <head></head> に配置
      HtmlLink css = new HtmlLink();
      css.Href = 
        cs.GetWebResourceUrl(rsType, 
          "SimpleControl.MyStylesheet.css");
      css.Attributes["rel"] = "stylesheet";
      css.Attributes["type"] = "text/css";
      Page.Header.Controls.Add(css);

      Literal tableStart = new Literal();
      tableStart.Text = 
        "<table cellspacing='0' cellpadding='0'>" + 
        "<tr><td valign='middle'>";
      this.Controls.Add(tableStart);
      HyperLink linkImage = new HyperLink();
      linkImage.NavigateUrl = 
        cs.GetWebResourceUrl(rsType, "SimpleControl.Help.htm");
      linkImage.Attributes.Add(
        "onmouseover", "ChangeImage('image1','Green')");
      linkImage.Attributes.Add(
        "onmouseout", "RollbackImage('image1','Red')");

      this.Controls.Add(linkImage);

      Image img = new Image();
      img.ImageUrl = 
        cs.GetWebResourceUrl(rsType, 
          "SimpleControl.smallFail.gif");
      img.ID = "image1";
      // ASP.NET 4 からは自動的に border-width を 0 に設定
      // しなくなった
      img.Style["border-width"] = "0";
      linkImage.Controls.Add(img);

      Literal ltTableColumn = new Literal();
      ltTableColumn.Text = "</td><td valign='middle'>";
      this.Controls.Add(ltTableColumn);

      HyperLink linkText = new HyperLink();
      linkText.NavigateUrl = 
        cs.GetWebResourceUrl(rsType, "SimpleControl.Help.htm");
      linkText.Attributes.Add(
        "onmouseover", "ChangeImage('image1','Green')");
      linkText.Attributes.Add(
        "onmouseout", "RollbackImage('image1','Red')");

      this.Controls.Add(linkText);

      Label lbl = new Label();
      lbl.ID = "Label1";
      lbl.Text = this.Text;
      lbl.Font.Name = "Verdana";
      lbl.Font.Bold = true;
      lbl.Font.Size = FontUnit.Medium;
      lbl.ForeColor = System.Drawing.Color.Red;
      linkText.Controls.Add(lbl);

      Literal tableEnd = new Literal();
      tableEnd.Text = "</td></tr></table>";
      this.Controls.Add(tableEnd);

      // MyScript.js ファイルを参照するスクリプト
      // ブロックを追加。
      cs.RegisterClientScriptInclude(
        "MyScript", 
        cs.GetWebResourceUrl(rsType, 
          "SimpleControl.MyScript.js"));

      base.CreateChildControls();
    }
  }
}

MyScript.js

このクライアントスクリプトは、mouseover、mouseout イベントで、コントロールの画像とラベルの色を変化させるためのものです。

img 要素の src 属性に、アセンブリ内のリソース smallFail.gif と smallSuccess.gif を参照する URL を動的に取得して設定している点に注意してください。

そのためには、スクリプトの中で、<%= WebResource("アセンブリ内のパス") %> のように記述します。それによって、<%= ~ %> ブロックの中身は実行時に動的に解析され、出力時には、

WebResource.axd?d=p_b6vJYrM2mWH00RZ2d...faEA2&t=634730293148422703

といったURLが埋め込まれます。その場合、後述しますが、WebResource 属性を定義する際、PerformSubstitution プロパティを true にしておく必要がありますので注意してください。

なお、ここでは簡略化のため Labal1 というサーバー側の ID を直接使っていますが、クライアント側の id は異なる場合がありますので(例:マスターページを使う場合など)、実際の応用には注意してください。

function ChangeImage(imgControl, varcolor) {
  document.getElementById(imgControl).src = 
    '<%=WebResource("SimpleControl.smallSuccess.gif")%>';
  document.getElementById('Label1').style.color = varcolor;
}

function RollbackImage(imgControl, varcolor) {
  document.getElementById(imgControl).src = 
    '<%=WebResource("SimpleControl.smallFail.gif")%>';
  document.getElementById('Label1').style.color = varcolor;
}

MyStylesheet.css

スタイルを何も指定しないと、ハイパーリンクの文字列には、mouseover でアンダーラインが入ります。この CSS が有効なのでアンダーラインは入りません。

a {
    text-decoration:none;
}

Help.htm

リソースに埋め込んだヘルプページです。コントロールをクリックすると、このページに遷移します。

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
	<head>
		<title>ヘルプのページ</title>
	</head>
	<body>
	    <h1>Help Page</h1>
        <p>これはヘルプページです。</p>
	</body>
</html>

AssemblyInfo.cs

アセンブリ内の指定された埋め込みリソースを Web リソースとして使用できるようにするため、WebResource 属性 を使用してメタデータ属性を定義します。

AssemblyInfo.cs ファイルは Visual Studio が自動生成しますので、その中に、以下のコードのように WebResource 属性の定義を追加します。(WebResourceAttribute クラス の説明のサンプルコードのように、クラスファイルに追加することもできます)

この中で、MyScript.js は、アセンブリ内のリソース smallFail.gif と smallSuccess.gif を参照している点が他と異なります。そのため、PerformSubstitution プロパティ を true にしています。

リソースのファイル名や名前空間(この例では SimpleControl)を間違えると、当然動きませんが、エラーもでないので原因が分からず、ハマったりするかもしれませんので注意してください。

また、リソースファイルを、プロジェクト直下でなく、別にフォルダを作ってそこに入れたりすると、名前空間が変わってきますので注意してください。例えば、プロジェクト直下に Resources というフォルダを作って、そこにリソースファイルを入れると、名前空間は SimpleControl ではなく、SimpleControl.Resources になります。

using System.Reflection;
using System.Runtime.CompilerServices;
using System.Runtime.InteropServices;
using System.Web.UI;

[assembly: AssemblyTitle("SimpleControl")]
[assembly: AssemblyDescription("")]
[assembly: AssemblyConfiguration("")]
[assembly: AssemblyCompany("")]
[assembly: AssemblyProduct("SimpleControl")]
[assembly: AssemblyCopyright("Copyright(c) 2012")]
[assembly: AssemblyTrademark("")]
[assembly: AssemblyCulture("")]

// 埋め込んだリソースが参照されるよう WebResource 属性を追
// 加。 これら以外は Visual Studio が自動生成したコード。
[assembly: WebResource("SimpleControl.Help.htm", 
    "text/html")]
[assembly: WebResource("SimpleControl.MyStylesheet.css", 
    "text/css")]
[assembly: WebResource("SimpleControl.smallFail.gif", 
    "image/gif")]
[assembly: WebResource("SimpleControl.smallSuccess.gif", 
    "image/gif")]

// MyScript.js は、アセンブリ内のリソース smallFail.gif
// と smallSuccess.gif を参照している。そのため、
// PerformSubstitution プロパティを true にしている。
[assembly: WebResource("SimpleControl.MyScript.js", 
    "text/javascript", PerformSubstitution = true)]
// WebResource 属性の追加はここまで。

[assembly: ComVisible(false)]

[assembly: Guid("3f6b9d6b-02db-48f8-acba-9cfe139a199a")]

[assembly: AssemblyVersion("1.0.0.0")]
[assembly: AssemblyFileVersion("1.0.0.0")]

Tags:

Web Custom Control

多言語対応カスタムコンロトール

by WebSurfer 2010年12月6日 21:33

リソースファイルを用いて多言語対応させた Web カスタムコントロールの話です。

多言語対応カスタムコンロトール

Culture, UICulture の値として "auto" を設定した場合には、ASP.NET がリクエスト情報に含まれる Accept-Language ヘッダの内容から判断して、自動的にカルチャを特定してくれます。

上の画像は、IE8 で言語をフランス語(フランス) fr-FR に設定してページを要求した結果、サーバー側でカルチャが fr-FR と判断され、カスタムコントロールのリソースファイル Resource.fr-FR.resx に定義されたフランス語のリソース(今回の例では、文字列 Adresse e-mail, Enregister)が表示されたものです。

この例に使ったカスタムコントロールの構成は以下の通りです。リソースの基になるのが Resource.resx で、これはソリューションエクスプローラ上で手動で追加します。その下の Resource.Designer.cs は「厳密に型指定されたリソースクラス」というもので、Resource.resx を追加すると自動生成されます。

カスタムコントロールの構成

Resource.resx にはデフォルトのリソースを定義します。今回の例では、下の画像に示すように LabelText, ButtonText という名前の 2 つの文字列です。要求されたカルチャのリソースファイルがない場合(この例では en-US, fr-FR, ja-JP 以外が指定された場合)、Resource.resx に定義されたリソースが使われます。

デフォルトのリソース

Resource.Designer.cs には Resource という名前のクラスが定義され、その中にカルチャを設定する Culture 静的プロパティ、ローカライズされたリソースを取得する静的プロパティ(この例では Resource.resx で設定した LabelText, ButtonText と同じ名前のプロパティ)が定義されます。

ローカライズされたリソースは個別のリソースファイルを追加して定義します。例えば、フランス語(フランス)の場合は Resource.fr-FR.resx という名前のリソースファイルを追加し、以下の画像のように Resource.resx で設定した LabelText, ButtonText と同じ名前でフランス語の文字列を設定します。

フランス語(フランス)の場合の Resource.fr-FR.resx

今回の例に用いた MyServerControl.cs の内容は以下の通りです。以下のコードで、Resource というのが Resource.Designer.cs で定義される「厳密に型指定されたリソースクラス」です。

Resource.Culture 静的プロパティで現在のカルチャを設定し、設定されたカルチャのリソースファイルからローカライズされた文字列を、Resource.LabelText, Resource.ButtonText 静的プロパティを用いて取得しています。

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
using System.Text;
using System.Web;
using System.Web.UI;
using System.Web.UI.WebControls;
using System.Threading;
using System.Globalization;

namespace MultiLanguageServerControl
{
  public class MyServerControl : WebControl
  {
    public virtual string LabelText
    {
      get
      {
        string s = (string)ViewState["LabelText"];
        if (s == null)
        {
          Resource.Culture = Thread.CurrentThread.CurrentUICulture;
          s = Resource.LabelText;
        }
        return s;
      }
      set
      {
        ViewState["LabelText"] = value;
      }
    }

    public virtual string ButtonText
    {
      get
      {
        string s = (string)ViewState["ButtonText"];
        if (s == null)
        {
          Resource.Culture = Thread.CurrentThread.CurrentUICulture;
          s = Resource.ButtonText;
        }
        return s;
      }
      set
      {
        ViewState["ButtonText"] = value;
      }
    }

    protected override HtmlTextWriterTag TagKey
    {
      get
      {
        return HtmlTextWriterTag.Table;
      }
    }

    protected override void RenderContents(HtmlTextWriter writer)
    {
      StringBuilder sb = new StringBuilder();
      sb.AppendLine("<tr>");
      sb.AppendFormat("<td><span id=\"Label1\">{0}</span></td>", 
        LabelText);
      sb.Append("<td><input type=\"text\" id=\"TextBox1\" /></td>");
      sb.AppendFormat(
        "<td><input type=\"submit\" value=\"{0}\" id=\"Button1\" /></td>", 
        ButtonText);
      sb.AppendLine("</tr>");
      writer.Write(sb.ToString());
    }
  }
}

このソリューションをビルドすると、以下のような dll が生成されます。この MultiLanguageServerControl.dll と en-US, fr-FR, ja-JP フォルダを中身ごと Web アプリケーションの Bin フォルダにコピーします。

ソリューションをビルドした結果

aspx ページでは以下のように Culture, UICulture を "auto" に設定し、ブラウザの言語を希望の言語(今回の例では、ja-JP または en-US または fr-FR)に設定してページを要求すれば、当該リソースファイルからローカライズされた文字列が取得されます。例えば fr-FR とすれば、一番上の画像のように表示されます。(設定を忘れるとどうなるかは、この記事の下の方の「2016/6/17 追記」を見てください)

<%@ Page Language="C#" Culture="auto" UICulture="auto" %>
<%@ Import Namespace="System.Globalization" %>
<%@ Import Namespace="System.Threading" %>
<%@ Register Assembly="MultiLanguageServerControl" 
  Namespace="MultiLanguageServerControl" 
  TagPrefix="cc2" %>

<!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 Page_Load(object sender, EventArgs e)
  {
    CultureInfo ci = Thread.CurrentThread.CurrentUICulture;
    Label1.Text = "現在の CultureInfo: " + ci.ToString();
  }
</script>

<html xmlns="http://www.w3.org/1999/xhtml">
<head runat="server">
  <title></title>
</head>
<body>
  <form id="form1" runat="server">
  <div>
    <asp:Label ID="Label1" runat="server" />
    <br /><br />
    <cc2:MyServerControl runat="server" />
  </div>
  </form>
</body>
</html>

aspx ページ本体の多言語化の話は別途・・・というより、@IT の記事 リソース・ファイル活用で国際化対応ページを構築するには? を見たほうが早いかも。

-------- 2016/6/17 追記 --------

Culture, UICulture を "auto" に設定すると、ASP.NET は、ブラウザから送信されてくる要求ヘッダに含まれる Accept-Language の設定を調べて、その要求を処理するスレッドのカルチャを Accept-Language に設定されているカルチャに書き換えるようです。

そして、リソースマネージャが実行時に、Thread.CurrentUICulture などで得られる CultureInfo(現在の要求を処理しているスレッドのカルチャ情報)を参照してローカライズされたリソースを検索し、UI に表示されるテキストを取得するという仕組みになっています。

Culture, UICulture を "auto" に設定するのを忘れるとブラウザの言語設定は無視されます。デフォルトではシステムのロケールに該当するカルチャがスレッドに設定されますので、例えば日本語 OS で xxx.ja-JP.resx というリソースがあれば、常にそれから UI に表示されるテキストを取得します。

Web サイトが日本語専用でサーバーも日本にあれば忘れても問題ないかもしれませんが、ホスティングサービス(Azure も含む)でサーバーが外国にある場合は Culture, UICulture を "auto" に設定するのを忘れると問題が出ると思います。

ロケール、カルチャ、Culture と UICulture の違いなどについては、以下の記事が参考になりましたので、忘れないようにリンクを張っておきます。

カルチャの基本とカルチャ情報

Tags: ,

Web Custom Control

About this blog

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

Calendar

<<  2017年12月  >>
262728293012
3456789
10111213141516
17181920212223
24252627282930
31123456

View posts in large calendar