カスタムコントロールのプロパティの型をコントロールにしたい時に必要なTypeConverterの実装

以前に、.NET Framework1.1を利用している時に下記のエントリをしました。

カスタムコントロールのプロパティの型をコントロールにしたい時に必要なTypeConverterの抽象クラスです。

各、コントロール毎にTypeConverterを一から作るのはめんどくさいので、抽象クラスを作っておいて、継承先で型を指定するプロパティをオーバライドする事で指定の型のTypeConverterを作成しています。

バージョンは1.1:カスタムコントロールのプロパティの型をコントロールにしたい時に必要なTypeConverterの抽象クラス – NAL-6295の舌先三寸

今回、ジェネリックを利用する事で、抽象クラスである必要が無くなり、継承する必要がなくなりました。

利用するときに、その型を指定すればよいだけです。

using System;
using System.Drawing;
using System.Reflection;
using System.Collections;
using System.ComponentModel;
using System.ComponentModel.Design;
using System.ComponentModel.Design.Serialization;
namespace NAL6295.Web.UI.Controls
{
/// <summary>
/// コントロールの型情報と型名の相互変換
/// </summary>
/// <typeparam name="T">System.Web.UI.Controlを継承したクラスのみ指定可能</typeparam>
public class ControlConverter<T> : System.ComponentModel.StringConverter where T:System.Web.UI.Control    {
private ArrayList _values;
/// <summary>
///		値リストのサポートを許可する
/// </summary>
/// <param name="context"></param>
/// <returns></returns>		
public override bool GetStandardValuesSupported(System.ComponentModel.ITypeDescriptorContext context)
{
return true;
}
/// <summary>
///		値リストの作成
/// </summary>
/// <param name="context"></param>
/// <returns>デザイン画面に存在するControlTypeプロパティで指定されたコントロールのリストを取得しそのIDリストを返す</returns>
public override System.ComponentModel.TypeConverter.StandardValuesCollection GetStandardValues(System.ComponentModel.ITypeDescriptorContext context)
{
_values = new ArrayList();
foreach (System.Web.UI.Control item in GetControlList(context))
{
_values.Add(item.ID);
}
return new System.ComponentModel.TypeConverter.StandardValuesCollection(_values);
}
/// <summary>
///		文字列型から指定された型への変換
///		選択リストに無い文字列が指定されていたときは、String.Emptyを返す
/// </summary>
/// <param name="context"></param>
/// <param name="culture"></param>
/// <param name="value"></param>
/// <returns></returns>
public override object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
{
if (value is string)
{
foreach (System.Web.UI.Control item in GetControlList(context))
{
if (item.ID == (string)value)
{
return base.ConvertFrom(context, culture, value);
}
}
return string.Empty;
}
return base.ConvertFrom(context, culture, value);
}
/// <summary>
///		指定された型のリストを返す
/// </summary>
/// <param name="context"></param>
/// <returns></returns>
private object[] GetControlList(ITypeDescriptorContext context)
{
IReferenceService serv = (IReferenceService)context.GetService(typeof(IReferenceService));
return serv.GetReferences(typeof(T));
}
}
}

利用例:

public class カスタムコントロール : Control
{
//いろんな処理
private _hintControlName = "";
[TypeConverter(typeof(ControlConverter<Label>))
,DefaultValue("")
,Description("このコントロールのヒントを表示するラベルを選択するプロパティです。")]
public string Hint
{
get
{
return _hintControlName;
}
set
{
_hintControlName = value;
}
}
//いろんな処理
}
Share

型付データセットを使うことで複雑なSQLはコードの外に追い出す

私のシステム設計の指針は複雑度の分散です。

だからコードの中に複雑なクエリは書きません。

問い合わせのクエリは、何らかの目的があって発行するものなので、JOIN等複数のテーブルが絡む複雑なクエリは、すべてRDBMS側にViewとして持つようにしています。

追記:2009年あたりから、ストアドも対象とするようにしました。

そのためのツールとして型付データセットを利用しています。

そうすることで、コードの中では常に一つのオブジェクト(テーブルであれVIEWであれ)に対して操作をするだけで済むので複雑なクエリが発行される事はありません。

それを実現するために、SQLを直接書かないでクエリを発行するためのフレームワークを作成してあります。

詳しくは書きませんが、型付データセット内で定義されているテーブル単位にラップしているクラスがあり、それに条件を積み上げたオブジェクトをアタッチする事で、様々なクエリを発行するようになっています。

ラップしているクラス自体の定義は前もって用意してあるテーブルにアクセスするための機能を持った抽象クラスを継承してテーブルを指定しているだけなので、数行で済むため作成やメンテナンスの労力はほぼありません。

SQLServerやOracleといった製品毎の差異もフレームワーク側で吸収(.NET Framework2.0にはその機能が実装されていて、そっちに移行しようか悩みました。)し、その中でクエリを自動生成するようにしているため、システムをコーディングするときは、仕様にあうように条件オブジェクトをブロックのように積み上げて、ラップしているクラスにアタッチするだけで良いです。

また、内部でパラメタライズドクエリ(+必要なエスケープ)を作成するためSQLInjectionといった問題も発生しません。

また、ラップするクラス名はテーブルやビューと同じ名前にしてある事と、ビューは目的に応じた名称にしている事から、何が目的でデータにアクセスしているのかが一目瞭然になっています。

この取り組みはもう4年以上続けていますが、

  • 複雑なクエリは外(RDBMS側)に目的に応じたVIEWとして持っている
  • クエリがコードの中に点在しない
  • コード内でデータにアクセスしている部分へのアクセスやコントロールがしやすい

などから昔のシステムでも保守にかける時間が少なくて済んでいます。

また、開発時にコードが複雑になりすぎないため、それ以前に比較すると不具合が少なかったり、不具合が発生してもそれにかける作業時間が少なく済んでいます。

コードの中に複雑なクエリを書いてしまって、修正が大変だとか嘆いている、そこのあなた。

コードにとっての保守フェーズはコードは書いた瞬間から始まっています。

とりあえず複雑なクエリはRDBMSに持ってもらう事でコードの見通しを良くしてみてはいかがでしょうか。

Share

オブジェクトのDeserialize時に処理を付加したい場合

System.Runtime.Serialization.IDeserializationCallbackインタフェースを実装し、

OnDeserializationメソッド内で処理を付加します。

私の場合、

DataViewのフィルタ情報がシリアライズ時に保持されないので、

シリアライズ前に別変数に保持しておきます。

オブジェクトを復元(逆シリアル化)をする時に、

OnDeserialization内で、別変数に保持しておいた内容を

DataViewに設定しなおすようにしています。

使い方としてはこんなところです。

ASP.NETのSessionはInProc以外だとシリアライズする事で保存され、

逆シリアル化する事で復元されるようになるので、DataViewの内容が必要な時は上記の処理が行われるようにライブラリ化してあります。

Share

「[IE] Input type = file 属性の入力フォームにて submit を行っても Web サーバーから反応がない」の回避方法

IE6 (XP SP2)以降からの仕様で、

[IE] Input type = file 属性の入力フォームにて submit を行っても Web サーバーから反応がない

http://support.microsoft.com/default.aspx?scid=kb;ja;890981

というのがあり、

存在しないファイル名や、相対パスを入力した状態でsubmitを行うとJavaScriptエラーになる。

一度JavaScriptエラーになると、ポストバックができなくなるので、困ってしまう。

そんなとき、通常、submitをtry~catchで囲めば回避できるのだが、

ASP.NETの場合、クライアント側のPostBack関数内でsubmitを行っている関係上、難しい。(というか、その方法は無理である。)

では、どう回避するのかと言うと・・・

window.onerrorイベントに、回避するためのfunctionを設定する。

window.onerror = function(Message,FileName,RowNo)
{
alert(Message + 'n' + FileName + 'n行番号:' + RowNo);
}

といった感じである。

これでとりあえず、JavaScriptエラーは回避される。

今回の場合は”アクセスが拒否されました。”というエラーメッセージが出るので、

それをハンドリングして違うメッセージを出力しても良いかもしれない。

Share

DataRelationとDataColumn.Expressionプロパティを利用したグループ化処理のサンプル

DBからすでにデータを持ってきた子テーブルという名前のDataTableがある前提(サンプルではねつ造している。)

番号でグルーピングして、数量計を取りたい。

001 Dim 子テーブル As New DataTable
002         子テーブル.Columns.Add(New DataColumn("番号"))
003         子テーブル.Columns.Add(New DataColumn("枝番"))
004         子テーブル.Columns.Add(New DataColumn("数量", GetType(Decimal)))
005 
006         For 番号 As Integer = 1 To 10
007             For 枝番 As Integer = 1 To 10
008                 Dim 新規行 As DataRow = 子テーブル.NewRow()
009                 新規行("番号") = 番号
010                 新規行("枝番") = 枝番
011                 新規行("数量") = 番号 * 100 + 枝番
012                 子テーブル.Rows.Add(新規行)
013             Next
014         Next
015 
016 
017         Dim 親テーブル As New DataTable
018         親テーブル.Columns.Add(New DataColumn("番号"))
019         親テーブル.Columns.Add(New DataColumn("数量計", GetType(Decimal)))
020         For 番号 As Integer = 1 To 10
021             Dim 新規行 As DataRow = 親テーブル.NewRow()
022             新規行("番号") = 番号
023             親テーブル.Rows.Add(新規行)
024         Next
025 
026         Dim ds As New DataSet
027         ds.Tables.Add(子テーブル)
028         ds.Tables.Add(親テーブル)
029 
030         Dim 番号でグループ化するためのリレーション As New DataRelation("サンプル", 親テーブル.Columns("番号"), 子テーブル.Columns("番号"))
031 
032         親テーブル.ChildRelations.Add(番号でグループ化するためのリレーション)
033         親テーブル.Columns("数量計").Expression = "SUM(Child.数量)"
034 
035         For Each 現在行 As DataRow In 親テーブル.Rows
036             Console.WriteLine(String.Format("番号:{0} 数量計:{1}", 現在行("番号"), 現在行("数量計")))
037         Next
038 
039 

正直な話親のDataTableを作ろうと思ったら結局ループ処理が必要になるので、expressionを実際に使った事は無い。

ただ、こういう事もできるという話。

でも、最初から、親テーブルも子テーブルもあるなら、使えるかも。

Share

バージョンは1.1:カスタムコントロールのプロパティの型をコントロールにしたい時に必要なTypeConverterの抽象クラス

カスタムコントロールのプロパティの型をコントロールにしたい時に必要なTypeConverterの抽象クラスです。

各、コントロール毎にTypeConverterを一から作るのはめんどくさいので、抽象クラスを作っておいて、継承先で型を指定するプロパティをオーバライドする事で指定の型のTypeConverterを作成しています。

001 using System;
002 using System.Drawing;
003 using System.Reflection;
004 using System.Collections;
005 using System.ComponentModel;
006 using System.ComponentModel.Design;
007 using System.ComponentModel.Design.Serialization;
008 namespace NAL6295.Web.UI.Controls
009 {
010     /// <summary>
011     ///        コントロールのリストと選択されたコントロールの文字列を相互変換するコンバータの抽象クラス
012     /// </summary>
013     public abstract class ControlConverterAbstract:System.ComponentModel.StringConverter
014     {
015 
016         /// <summary>
017         ///        選択リストに追加したいコントロールの型情報
018         /// </summary>
019         protected abstract System.Type ControlType{get;}
020 
021 
022 
023         /// <summary>
024         ///        値リストのサポートを許可する
025         /// </summary>
026         /// <param name="context"></param>
027         /// <returns></returns>        
028         public override  bool GetStandardValuesSupported(System.ComponentModel.ITypeDescriptorContext context)
029         {
030             return true;
031         }
032 
033         /// <summary>
034         ///        値リストの作成
035         /// </summary>
036         /// <param name="context"></param>
037         /// <returns>デザイン画面に存在するControlTypeプロパティで指定されたコントロールのリストを取得しそのIDリストを返す</returns>
038         public override  System.ComponentModel.TypeConverter.StandardValuesCollection GetStandardValues(System.ComponentModel.ITypeDescriptorContext context)
039         {
040             ArrayList selectionList = new ArrayList();
041 
042             foreach(System.Web.UI.Control item in GetControlList(context))
043             {
044                 selectionList.Add(item.ID);
045             }
046 
047             return new System.ComponentModel.TypeConverter.StandardValuesCollection(selectionList);
048         }
049 
050         /// <summary>
051         ///        文字列型から指定された型への変換
052         ///        選択リストに無い文字列が指定されていたときは、String.Emptyを返す
053         /// </summary>
054         /// <param name="context"></param>
055         /// <param name="culture"></param>
056         /// <param name="value"></param>
057         /// <returns></returns>
058         public override  object ConvertFrom(ITypeDescriptorContext context, System.Globalization.CultureInfo culture, object value)
059         {
060             if(value is string)
061             {
062                 foreach(System.Web.UI.Control item in GetControlList(context))
063                 {
064                     if(item.ID == (string)value)
065                     {
066                         return base.ConvertFrom(context, culture, value);
067                     }
068                 }
069                 return string.Empty;
070             }
071             return base.ConvertFrom(context, culture, value);
072         }
073 
074         /// <summary>
075         ///        指定された型のリストを返す
076         /// </summary>
077         /// <param name="context"></param>
078         /// <returns></returns>
079         private object[] GetControlList(ITypeDescriptorContext context)
080         {
081             IReferenceService service = (IReferenceService)context.GetService(typeof(IReferenceService));
082             return service.GetReferences(ControlType);
083         }
084 
085 
086     }
087 
088 }
089 
090 

使用例

001     public sealed class TextBoxConverter:ControlConverterAbstract
002     {
003         protected override Type ControlType
004         {
005             get
006             {
007                 return typeof(TextBox);
008             }
009         }
010 
011     }

Share

URLEncodeしたページにアクセスした後、ポストバックする時はURLEncodeされていないURLでアクセスしてしまうのを回避する(検証はASP.NET1.1)

URLに日本語を利用していて、

URLEncodeしてwebフォームにアクセスしても、

そのままだとレンダリングされたformタグのaction属性が、

デコードされたURLになっている。

そのため、ポストバック時にアクセスするURLはEncodeされていない。

それを回避するにはglobal.asaxのPreRequestHandlerExecuteイベント内で、

下記のコードを記述すればよい。

System.Web.HttpContext.Current.RewritePath(System.Web.HttpContext.Current.Request.Url.AbsolutePath);

実際はそのイベントじゃなくても、ある程度許容範囲がある。

RequestEndイベント内でもOKだった。

Share

再改訂版:DataGrid内のラジオボタンでグループに出来ない問題の回避方法(ターゲットはASP.NET1.1です。)

■[ASP.NET][.NET Tips]改訂版:DataGrid内のラジオボタンでグループに出来ない問題の回避方法

■[ASP.NET][.NET Tips]過去のサンプルの修正DataGrid内のラジオボタンでグループに出来ない問題の回避方法

NAL-6295の舌先三寸 – つまらない事の中に重要な事がある。

というエントリで公開していたソースでは

テキスト部分をクリックすると最後のラジオボタンが選択される。

NAL-6295の舌先三寸 – 改訂版:DataGrid内のラジオボタンでグループに出来ない問題の回避方法

以前に(といっても1ヶ月前)この記事で改訂版を出していましたが、下記の制限がありました。

・プログラム側でcheckedの内容を変更すると、

次のポストバックのLoadViewState時にcheckedの内容が変更されて、

 クライアントで選択されたcheckedの内容を取得できなかった。

(EnabledViewState=falseでも)

そこで、下記の改訂版を出すことにしました。

Group化する場合のみ、ViewStateを無視する仕様になっています。

001 Imports System.ComponentModel
002 Imports System.Web.UI
003 
004 
005 <DefaultProperty("Checked"), ToolboxData("<{0}:RadioButtonEx runat=server></{0}:RadioButtonEx>")> _
006  Public Class RadioButtonEx
007     Inherits System.Web.UI.WebControls.RadioButton
008 
009     Public Overrides ReadOnly Property UniqueID() As String
010         Get
011             If Me.GroupName Is Nothing OrElse Me.GroupName.Length = 0 Then
012                 Return MyBase.UniqueID
013             Else
014                 Return Me.GroupName()
015             End If
016         End Get
017     End Property
018 
019     Public Overrides ReadOnly Property ClientID() As String
020         Get
021             If Me.GroupName Is Nothing OrElse Me.GroupName.Length = 0 Then
022                 Return MyBase.ClientID
023             Else
024                 Return MyBase.UniqueID
025             End If
026         End Get
027     End Property
028 
029     Protected Overrides Sub OnPreRender(ByVal e As System.EventArgs)
030         Me.Attributes.Add("value", MyBase.UniqueID)
031         MyBase.OnPreRender(e)
032     End Sub
033 
034     Protected Overrides Sub OnInit(ByVal e As System.EventArgs)
035         MyBase.OnInit(e)
036         If Me.Page.IsPostBack Then
037             Me.Checked = MyBase.UniqueID = Me.Page.Request.Form.Item(Me.GroupName)
038         End If
039     End Sub
040 
041 
042     Protected Overrides Sub LoadViewState(ByVal savedState As Object)
043         If Me.GroupName Is Nothing OrElse Me.GroupName.Length = 0 Then
044             MyBase.LoadViewState(savedState)
045         End If
046     End Sub
047 
048     Protected Overrides Function SaveViewState() As Object
049         If Me.GroupName Is Nothing OrElse Me.GroupName.Length = 0 Then
050             Return MyBase.SaveViewState()
051         Else
052             Return Nothing
053         End If
054     End Function
055 End Class
056 

Share

改訂版:DataGrid内のラジオボタンでグループに出来ない問題の回避方法

既に

http://d.hatena.ne.jp/NAL-6295/20060726

にて再度改訂済みです。

■[ASP.NET][.NET Tips]過去のサンプルの修正DataGrid内のラジオボタンでグループに出来ない問題の回避方法

NAL-6295の舌先三寸 – つまらない事の中に重要な事がある。

というエントリで公開していたソースでは

テキスト部分をクリックすると最後のラジオボタンが選択される。

という問題がありました。

それを解決したソースを以下に公開しておきます。

説明をすると、

ClientIDをオーバライドして、GroupNameプロパティが設定されているときのみMyBase.UniqueIDを返す処理

を追加しました。

001 Imports System.ComponentModel
002 Imports System.Web.UI
003 
004 
005 <DefaultProperty("Checked"), ToolboxData("<{0}:RadioButtonEx runat=server></{0}:RadioButtonEx>")> _
006  Public Class RadioButtonEx
007     Inherits System.Web.UI.WebControls.RadioButton
008 
009     Public Overrides ReadOnly Property UniqueID() As String
010         Get
011             If Me.GroupName Is Nothing OrElse Me.GroupName.Length = 0 Then
012                 Return MyBase.UniqueID
013             Else
014                 Return Me.GroupName()
015             End If
016         End Get
017     End Property
018 
019     Public Overrides ReadOnly Property ClientID() As String
020         Get
021             If Me.GroupName Is Nothing OrElse Me.GroupName.Length = 0 Then
022                 Return MyBase.ClientID
023             Else
024                 Return MyBase.UniqueID
025             End If
026         End Get
027     End Property
028 
029     Protected Overrides Sub OnPreRender(ByVal e As System.EventArgs)
030         Me.Attributes.Add("value", MyBase.UniqueID)
031         MyBase.OnPreRender(e)
032     End Sub
033 
034     Protected Overrides Sub OnInit(ByVal e As System.EventArgs)
035         MyBase.OnInit(e)
036         If Me.Page.IsPostBack Then
037             Me.Checked = MyBase.UniqueID = Me.Page.Request.Form.Item(Me.GroupName)
038         End If
039     End Sub
040 End Class
041 

Share

過去のサンプルの修正(DataGrid内のラジオボタンでグループに出来ない問題の回避方法)

注意:

http://d.hatena.ne.jp/NAL-6295/20060726

にて再度改訂済みです

表題の通りですが、

RadioButtonコントロールを継承して、以下のコードを書くと

グループ化できるし、ポストバック時にCheckedプロパティも

正常に動作します。

NAL-6295の舌先三寸 – DataGrid内のラジオボタンでグループに出来ない問題の回避方法

というエントリで掲載したサンプルは、デザイン時に描画エラーが出てしまう問題が発生していました。(勿論、実行時には使える)

そこで、今回、その制限を回避することが可能になりましたので、新しくサンプルを公開します。

過去に、先のエントリを参照された方は、こちらに置き換えると良いかもしれません。

001 Imports System.ComponentModel
002 Imports System.Web.UI
003 
004 
005 <DefaultProperty("Checked"), ToolboxData("<{0}:RadioButtonEx runat=server></{0}:RadioButtonEx>")> _
006 Public Class RadioButtonEx
007     Inherits System.Web.UI.WebControls.RadioButton
008 
009     Public Overrides ReadOnly Property UniqueID() As String
010         Get
011             If Me.GroupName Is Nothing OrElse Me.GroupName.Length = 0 Then
012                 Return MyBase.UniqueID
013             Else
014                 Return Me.GroupName()
015             End If
016         End Get
017     End Property
018 
019     Protected Overrides Sub OnPreRender(ByVal e As System.EventArgs)
020         Me.Attributes.Add("value", MyBase.UniqueID)
021         MyBase.OnPreRender(e)
022     End Sub
023 
024     Protected Overrides Sub OnInit(ByVal e As System.EventArgs)
025         MyBase.OnInit(e)
026         If Me.Page.IsPostBack Then
027             Me.Checked = MyBase.UniqueID = Me.Page.Request.Form.Item(Me.GroupName)
028         End If
029     End Sub
030 End Class
031 
032 

Share