ラベル .NET Framework の投稿を表示しています。 すべての投稿を表示
ラベル .NET Framework の投稿を表示しています。 すべての投稿を表示

2021年5月24日月曜日

Strings.StrConvで全角に変換するときにサロゲートペア文字が文字化けする

Strings.StrConvで全角に変換しようとする文字列にサロゲートペア文字が含まれていると"??"に変換されてしまいます。

Debug.WriteLine("𠀋=" + Strings.StrConv("𠀋", VbStrConv.Wide)); // 𠀋=??と表示される

「.NET Frameworkは文字列を内部的にUnicodeしており、標準機能のStrConvを使用しているからヨシ!」と思っていると失敗するので注意が必要です。

参考

Strings.StrConv


2019年8月12日月曜日

VB.NETのキライなところ: 構造体にNothingを代入できる

C#ではintなどの構造体にnullを代入しようとすると、コンパイルエラーが発生します。
int x = null; // コンパイルエラー

一方、VB.NETではnull許容型でない構造体にNothingを代入すると、デフォルト値が代入されます。
Dim x As Integer = Nothing ' x = 0

Nothing的な値との比較は以下のようになります。
Public Shared Sub Main()
    Dim n As Integer = Nothing

    ' コンパイルエラー(VBNC30020)
    ' If n Is Nothing Then
    '     Console.WriteLine("n is Nothing")
    ' End If
    If n = Nothing Then
        Console.WriteLine("n equals Nothing")
    End If
    If IsNothing(n) Then
        Console.WriteLine("isNothing n")
    End If
    If n = 0 Then
        Console.WriteLine("n equals zero")
    End If
End Sub
' 実行結果
' n equals Nothing
' n equals zero

n = Nothingは使えるけど、IsNothing(n)常にFalseを返すので使えない。
仕様を理解していても混乱してしまうのでキライです。
null許容でない構造体の初期化にはNothingを使わないようにします。

参考
Nothing(Visual Basic)

2015年11月9日月曜日

ASP.NETでファイルをダウンロードするボタン

クリックするとファイルをダウンロードするボタンを作りました。
主な機能は以下のとおりです。

  • クリックするとFileプロパティにセットされたファイルをダウンロードする
  • FileプロパティはClickイベントやDownloadingイベントでセットすることもできる
  • Downloadedイベントで動的に作成したファイルを削除することもできる
使用するには、以下の手順に従います。
  1. FileDownloadButtonをプロジェクトに追加する
  2. FileDownloadButtonをツールボックスへ追加する
    参考: 方法 : Visual Studio でカスタム ASP.NET サーバー コントロールを使用するの「カスタム コントロールをツールボックスに追加するには」
  3. *.aspxへFileDownloadButtonを配置し、イベントハンドラを設定する
    例:
    <cc1:FileDownloadButton ID="FileDownloadButton1" runat="server"
                            Text="Download"
                            OnDownloading="FileDownloadButton1_Downloading"
                            OnDownloaded="FileDownloadButton1_Downloaded"/>
    
  4. *.csでイベントハンドラを実装する
    例:
            protected void FileDownloadButton1_Downloading(object sender, EventArgs e)
            {
                // Tempファイルを作成する
                var path = Path.GetTempFileName();
                var file = new FileInfo(path);
    
                // Tempファイルへデータを出力する
                using (var s = file.OpenWrite())
                using (var w = new StreamWriter(s))
                {
                    w.WriteLine("Hello");
                    w.Flush();
                }
    
                // FileDownloadButtonのFileプロパティへTempファイルをセットする
                FileDownloadButton1.File = file;
            }
    
            protected void FileDownloadButton1_Downloaded(object sender, EventArgs e)
            {
                // Tempファイルを削除する
                FileDownloadButton1.File.Delete();
            }
    
Content-Typeの設定やレスポンスの呼び出しなど、低レベルな処理をカプセル化できるので、導入のメリットはそこそこあると思います。よろしければご利用ください。

以下、FileDownloadButtonのソースです。

2014年9月8日月曜日

.NETで改行を含む文字列を組み立てるならStringWriterがおすすめ

プログラムの実行時に文字列を組み立てるなら、StringBuilderクラスを使うのが定石です。しかし、改行で区切った文字列を組み立てようとすると、どうにも見苦しくなってしまいます。
var sb = new StringBuilder()
.Append("select *").Append(Environment.NewLine)
.Append("  from Table1").Append(Environment.NewLine)
.Append(" where Column1 = @Value1").Append(Environment.NewLine);


そんなときはStringWriterクラスを使うのがお勧めです。
var sw = new StringWriter();
sw.WriteLine("select *");
sw.WriteLine("  from Table1");
sw.WriteLine(" where Column1 = @Value1");

コードがスッキリ見やすくなりました。

参照
StringWriterクラス
StringBuilderクラス

2014年9月1日月曜日

ADO.NETのData ProviderでRDBへの依存をカプセル化するにはDbProviderFactoryを使う

これまでに経験したシステム開発では、DBを決めずに開発が始まることはなかったので、必要になる場面はなかなかないと思いますが、DbProviderFactoryを使えばRDBに依存しないコードが書けます。
次に例を示します。
    class Class1
    {
        private readonly DbProviderFactory factory;
        private readonly string connectionString;

        public Class1(DbProviderFactory factory, string connectionString)
        {
            this.factory = factory;
            this.connectionString = connectionString;
        }

        public List<object> Method1()
        {
            using (var conn = factory.CreateConnection())
            using (var command = factory.CreateCommand())
            {
                conn.ConnectionString = connectionString;
                conn.Open();

                command.Connection = conn;
                command.CommandText = "select * from Table1 where ID = @ID";

                var param = factory.CreateParameter();
                param.ParameterName = "ID";
                param.Value = "ID1";
                command.Parameters.Add(param);

                var reader = command.ExecuteReader();

                var result = new List<object>();
                while (reader.Read())
                {
                    var arr = new object[reader.FieldCount];
                    reader.GetValues(arr);
                    result.Add(arr);
                }

                return result;
            }
        }

実際にはSQLもRDBに合わせてコーディングしないといけないので、DbProviderFactoryだけでは不十分と思います。

2014年8月11日月曜日

ASP.NETでJavaScriptからポストバックを発生させる方法

ASP.NETアプリケーションをコーディングしていて、JavaScriptからポストバックしたいというケースがあります。例えば、検索結果の一覧をテーブルで表示しており、任意のセルをクリックした時に詳細を表示する画面へ遷移するケースなどです。

こういう状況に対応できるのが、次のクラスとインターフェイスです。
まず、JavaScriptのイベントハンドラに対して、ClientScriptManager.GetPostBackEventReferenceで取得した文字列を呼び出すように実装します。このとき、引数として渡した文字列が、次で実装するポストバックイベントハンドラの引数になります。ClientScriptManagerオブジェクトは、Page.ClientScriptから取得できます。
class MyPage : Page {

     protected void Page_PreRender(object sender, EventArgs e) {
         var args = "xxx"; // 例えば、詳細情報を表示するためのプライマリキー
         var js = ClientScript.GetPostBackEventReference(this, args);
         Table1.Rows[0].Cells[0].Attributes.Add("onclick", js);
     }

}

次に、ポストバックを受けるクラスが、IPostBackEventHandler.RaisePostBackEventを実装します。
// 上のクラスへIPostBackEventHandlerを実装
class MyPage : Page, IPostBackEventHandler { 
    ...
    public void RaisePostBackEvent(string eventArgs) {
        // 例えばセッションへeventArgsをセットして詳細ページへ遷移する
        Session.Add("key", eventArgs);
        var detail = ResolveUrl("~/MyDetailPage.aspx");
        Server.Transfer(detail);
    }
}
以上でJavaScriptからのポストバックが機能します。私が最初に思いついたのは、CSSで非表示にしたボタンをJavaScriptからクリックするという方法でした。しかし、こちらのほうが何倍もスマートだと思います。
ClientScriptManagerには、他にも様々なメソッドがあります。まだまだ便利な機能がありそうです。

参考:
ClientScriptManager
IPostBackEventHandler

2014年8月4日月曜日

ASP.NETアプリケーションの起動時にWeb.configファイルを読み込むには

ASP.NETアプリケーションの起動時に設定ファイルを読み込み、システムの初期設定をしたいというケースがあると重います。

設定ファイルを読み込むための、WebConfigurationManager.OpenWebConfigurationメソッドには、引数として設定ファイルの仮想パスが必要です。仮想パスの取得方法として、ASP.NET Web プロジェクトの仮想パスに手順が記載されていますが、アプリケーションの起動時には適用できません。なぜなら、紹介されている方法ではHttpRequestクラスを使用するのですが、アプリケーション起動時には、HttpRequestオブジェクトが使える状態にないからです。

そこで、代わりに使用するのが、HttpRuntimeクラスです。このクラスの、AppDomainVirtualPathプロパティから、アプリケーションの仮想パスを取得できるので、あとはファイル名を結合するだけです。結合には、VirtualPathUtilityクラスを使うとベターでしょう。
var path = VirtualPathUtility.Combine(HttpRuntime.AppDomainVirtualPath,
                                      "Web.config");
var conf = WebConfigurationManager.OpenWebConfiguration(path);

仮想パスをいい加減に扱うと、サーバーへインストールしたら動かなくなることがあります。HttpRuntimeクラスを使い、環境の変化に強いコードを書きましょう。

2014年7月21日月曜日

C#でアプリケーション設定ファイルを拡張する方法

.NETには標準で設定ファイルを読み込むためのAPIが公開されています。
そのままでも単純なキー=値形式の定義できます。
カスタマイズするには、主に次の3つのクラスを使用します。
  • ConfigurationSectionクラス
  • ConfigurationElementクラス
  • ConfigurationElementCollectionクラス
ConfigurationSectionクラス
カスタマイズした設定項目のルートになるクラスです。
    class NetworkConfigurationSection : ConfigurationSection
    {
        [ConfigurationProperty("LocalHost")]
        public NetworkConfigElement LocalHost
        {
            get { return base["LocalHost"] as NetworkConfigElement; }
            set { base["LocalHost"] = value; }
        }

        [ConfigurationProperty("Networks")]
        public NetworkConfigElementCollection Networks
        {
            get { return base["Networks"] as NetworkConfigElementCollection; }
        }
    }


ConfigurationElementクラス
設定値をもつXMLエレメントに対応するクラスです。
    class NetworkConfigElement : ConfigurationElement
    {
        [ConfigurationProperty("IPAddress")]
        public string IPAddress
        {
            get { return base["IPAddress"] as string; }
            set { base["IPAddress"] = value; }
        }
    }


ConfigurationElementCollectionクラス
複数のConfigurationElementをまとめるクラスです。
    class NetworkConfigElementCollection : ConfigurationElementCollection
    {
        protected override ConfigurationElement CreateNewElement()
        {
            return new NetworkConfigElement();
        }

        protected override object GetElementKey(ConfigurationElement element)
        {
            NetworkConfigElement elm = element as NetworkConfigElement;
            if (null == elm) return null;
            return elm.IPAddress;
        }
    }



2014年4月7日月曜日

GZIP圧縮に対応したHTTPクライアントを作る

前回の記事で予告したとおり、今回はC#でGZIP圧縮に対応したHTTPクライアントを作ります。
ポイントは3つあります。

  1. GZipStreamを使って入力データを圧縮する
  2. HttpClientHandlerのAutomaticDecompressionプロパティを設定する
  3. HTTPリクエストヘッダのContent-Encodingをgzipに設定する

GZipStreamを使って入力データを圧縮する
圧縮用にMemoryStreamをインスタンス化し、GZipStreamでラップします。
usingをネストしているのはGZipStreamをCloseするタイミングで圧縮が実行されるからです。
byte[] data = null;

using (var memory = new MemoryStream()) {
  using (var gzip = new GZipStream(memory, CompressionMode.Compress)) {
    int input = 0;
    byte[] buffer = new byte[1];

    while (-1 != (input = Console.Read())) {
      buffer[0] = Convert.ToByte(input);
      gzip.Write(buffer, 0, buffer.Length);
    }
  }
  data = memory.ToArray();
}

HttpClientHandlerのAutomaticDecompressionプロパティを設定する
リクエストボディの圧縮は実装する必要がありますが、レスポンスの解凍は.NET Frameworkが対応しています。
var clientHandler = new HttpClientHandler();
clientHandler.AutomaticDecompression = DecompressionMethods.Deflate | DecompressionMethods.GZip;

HTTPリクエストヘッダのContent-Encodingをgzipに設定する
コンテンツがgzip圧縮されていることを示すため、Content-Encodingをgzipに設定します。
using(var client = new HttpClient(clientHandler))
using(var content = new ByteArrayContent(data))
{
  content.Headers.ContentEncoding.Add("gzip");
  using(var message = client.PostAsync(url, content).Result)
  {
    Console.WriteLine(message.Content.ReadAsStringAsync().Result);
  }
}

一般的に、HTTPリクエストが圧縮を必要とするほど大きくなることはありません。
全ての画面データを毎回やりとりするような設計であれば、効果があるかもしれません。

プログラム全体を以下に記載します。

2014年2月17日月曜日

IronPythonで.NETモジュールをテストする

MSDN Magazineの記事を読んでいたら、タイトルのような記事を見つけました。
どれどれ、と読んでみるとせっかくIronPythonを使用しているのに、unittestモジュールを使用していないではありませんか。
もしかしたら当時のIronPythonには、まだunittestモジュールが含まれていなかったのかもしれません。
unittestモジュールを使用すれば、もっと簡単にテストが書けると思われるので、試してみます。
unittestモジュールの詳しい説明は割愛します。公式のドキュメントなどを参照してください。

テストコード
unittestモジュールを使用して書きなおしたテストが以下になります。
元の記事と同様、3番目のテストケースはスキップするようにしました。
# -*- coding: shift-jis -*-

import clr
import unittest

clr.AddReferenceToFileAndPath('Class1.dll')
from TwoCardPokerLib import Hand

class HandTest(unittest.TestCase):
    def test_royal_flush_wins_pair_aces(self):
        h1 = Hand('Ac', 'Kc')
        h2 = Hand('Ad', 'As')
        self.assertEquals(1, h1.Compare(h2))

    def test_straight_flush_diamonds_eq_straight_flush_hearts(self):
        h1 = Hand('Td', '9d')
        h2 = Hand('Th', '9h')
        self.assertEquals(0, h1.Compare(h2))

    @unittest.skip('')
    def test_straight_loses_ace_high(self):
        h1 = Hand('Ah', '2c')
        h2 = Hand('As', '9c')
        self.assertEquals(1, h1.Compare(h2))

    def test_flush_9_6_high_loses_flush_9_7(self):
        h1 = Hand('9h', '6h')
        h2 = Hand('9d', '7d')
        self.assertEquals(-1, h1.Compare(h2))

    def test_king_high_loses_ace_high(self):
        h1 = Hand('Kc', 'Jh')
        h2 = Hand('As', '5d')
        self.assertEquals(-1, h1.Compare(h2))


テスト結果
テストの実行結果は以下のようになります。
C:\>ipy.exe -m unittest -v testhand
test_flush_9_6_high_loses_flush_9_7 (testhand.HandTest) ... FAIL
test_king_high_loses_ace_high (testhand.HandTest) ... ok
test_royal_flush_wins_pair_aces (testhand.HandTest) ... ok
test_straight_flush_diamonds_eq_straight_flush_hearts (testhand.HandTest) ... ok
test_straight_loses_ace_high (testhand.HandTest) ... skipped ''

======================================================================
FAIL: test_flush_9_6_high_loses_flush_9_7 (testhand.HandTest)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "c:\testhand.py", line 29, in test_flush_9_6_high_loses_flush_9_7
    self.assertEquals(-1, h1.Compare(h2))
AssertionError: -1 != 0

----------------------------------------------------------------------
Ran 5 tests in 0.164s

FAILED (failures=1, skipped=1)

元記事との比較
元記事と比較して、テストランナーを実装していない分、工数を減らす効果はあったと思います。
反面、テストケースの追加については、元記事ではテキストファイルに1行追加するだけなのに対して、unittestを使用した場合はメソッドの定義が必要になっています。サードパーティ製のユニットテストフレームワークであるpytestはパラメータ化されたテストにも対応しているようなので、検討する価値があると思いました。

参考

2014年2月10日月曜日

Traceを使用してログを出力するときの設定例(Filter)

ログに要求される機能の1つとして、レベルによるフィルタリングがあります。
Traceを使用したログ出力にも、同様の機能があります。
具体的には、<filter>タグを使用します。

設定例
<configuration>
  <system.diagnostics>
    <trace autoflush="true">
      <listeners>
        <clear />

        <add name="file"
             type="System.Diagnostics.TextWriterTraceListener"
             initializeData="TEXT-WRITER.log">
            <!-- Warning以上を出力 -->
            <filter type="System.Diagnostics.EventTypeFilter"
                    initializeData="Warning" />
        </add>

      </listeners>
    </trace>
  </system.diagnostics>

</configuration>

出力例
TraceTest Warning: 0 : TraceWarning
TraceTest Error: 0 : TraceError

Informationレベルのログが出力されなくなりました。

参考
<filter> Element for <add> for <listeners> for <trace>

関連記事
Traceを使用してログを出力するときの設定例(TraceListener)
Traceを使用してログを出力するときの設定例(TraceOutputOptions)

2014年2月3日月曜日

Traceを使用してログを出力するときの設定例(TraceOutputOptions)

Traceを使用してログを出力するときの設定例(TraceListener)でTraceListenerの設定方法を書きました。
しかし、あれだけでは最低限のメッセージしか表示されません。
この記事では、日時などを表示する方法について書きます。

メッセージ以外を表示するには、<listeners>の<add>のtraceOutputOptions属性を使用します。



<configuration>

  <system.diagnostics>
    <trace autoflush="true">
      <listeners>
        <clear />

        <!-- テキストファイル -->
        <add name="file"
             type="System.Diagnostics.TextWriterTraceListener"
             initializeData="TEXT-WRITER.log"
             traceOutputOptions="Callstack, DateTime, LogicalOperationStack, ProcessId, ThreadId, Timestamp" />

      </listeners>
    </trace>
  </system.diagnostics>

</configuration>

この属性を設定した時の出力例は、以下のようになります。

TraceTest.exe Information: 0 : TraceInformation
    ProcessId=5700
    LogicalOperationStack=
    ThreadId=1
    DateTime=2014-02-02T12:47:28.3398027Z
    Timestamp=6032477769
    Callstack=   場所 System.Environment.GetStackTrace(Exception e, Boolean needFileInfo)
   場所 System.Environment.get_StackTrace()
   場所 System.Diagnostics.TraceEventCache.get_Callstack()
   場所 System.Diagnostics.TraceListener.WriteFooter(TraceEventCache eventCache)
   場所 System.Diagnostics.TraceListener.TraceEvent(TraceEventCache eventCache, String source, TraceEventType eventType, Int32 id, String message)
   場所 System.Diagnostics.TraceInternal.TraceEvent(TraceEventType eventType, Int32 id, String format, Object[] args)
   場所 TraceTest.TraceTest.Main(String[] args)

属性の詳しい説明については、TraceOutputOptions Valuesを参照してください。

2014年1月20日月曜日

Traceを使用してログを出力するときの設定例(TraceListener)

.Netでログを出力するときの選択肢の1つとして、System.Diagnostics.Traceクラスを使用する方法があります。
この時のアプリケーション構成ファイルの記述例を記載します。
テストプログラムとして、次のプログラムを使用します。

namespace TraceTest 
{
  using System.Diagnostics;
  
  public class TraceTest {
    
    public static void Main(string[] args) {
      Trace.TraceInformation("TraceInformation");
      Trace.TraceWarning("TraceWarning");
      Trace.TraceError("TraceError");
    }
  }
}

次にTraceListenerの設定例を記載します。

<configuration>

  <system.diagnostics>
    <trace autoflush="true">
      <listeners>
        <clear />

        <!-- 標準出力 -->
        <add name="console"
             type="System.Diagnostics.ConsoleTraceListener" />

        <!-- 区切りリスト -->
        <add name="delimited-list"
             type="System.Diagnostics.DelimitedListTraceListener"
             initializeData="DELIMITED-LIST.log" />

        <!-- イベントログ -->
        <add name="eventlog"
             type="System.Diagnostics.EventLogTraceListener"
             initializeData="Application" />

        <!-- テキストファイル -->
        <add name="file"
             type="System.Diagnostics.TextWriterTraceListener"
             initializeData="TEXT-WRITER.log" />

        <!-- XML形式 -->
        <add name="xml-writer"
             type="System.Diagnostics.XmlWriterTraceListener"
             initializeData="XML-WRITER.log" />

      </listeners>
    </trace>
  </system.diagnostics>

</configuration>
参考
<trace> の <listeners> の <add> 要素

2014年1月13日月曜日

FormでShowing/Hidingイベントを実装

System.Windows.Forms.Formにありそうでないイベントの一つに、表示前/非表示前イベントがあります。
なければ作っちゃえということで、SetVisibleCoreをオーバーライドして実装してみました。
処理の流れは以下のようになります。

  1. 引数と現在のVisibleを比較し、等しければイベントを発生させずにSetVisibleCoreを呼んで終了する。
  2. 引数がtrueのとき、Showingイベントを発生させる
  3. 引数がfalseのとき、Hidingイベントを発生させる
  4. 2,3の結果、CancelEventArgs.Cancelがtrueのとき、引数を逆にしてSetVisibleCoreを呼び、終了する。
  5. SetVisibleCoreを呼び、終了する。

なお、注意点として、SetVisibleCoreでShow/Hideメソッドを呼ばないこと、Visibleプロパティを更新しないことがあります。これらに違反すると、StackOverflowExceptionが発生してしまいます。

サンプル


参考

Form.SetVisibleCore メソッド