MessageBodyStream を MemoryStream (からByte[])に変換する

WCFサービスから MemoryStream を受け取ると、クライアント側では MessageBodyStream 型で取れてくる。
インターフェースは同じ Stream なんだけど、 MessageBodyStream を直接 MemoryStream にはキャストできない。(最終的にはHTTP Responseにセットしたいので、Byte にしたい)

なので、 MessageBodyStream をぐるぐる回しながら読み取って MemoryStream に入れ直し、最終的にToArray() で Byte を作っていたんだけど。

.NET4 から CopyTOなるメソッドが追加されていたことを知る。
c# - Creating a byte array from a stream - Stack Overflow

public static byte[] ReadFully(Stream input)
{
    using (MemoryStream ms = new MemoryStream())
    {
        input.CopyTo(ms);
        return ms.ToArray();
    }
}

だいぶスッキリした。痒い所に手が届く.NET。

ポリモーフィズムで条件分岐をなくす

using System;

class Program
{
    enum Programmer { Java, CSharp, VBnet };

    static void Main(string[] args)
    {
        Programmer[] programmers = 
            { 
                Programmer.Java,
                Programmer.CSharp,
                Programmer.VBnet,
                Programmer.CSharp
            };

        foreach (var p in programmers)
            Coding(p);

        Console.ReadKey();
    }

    private static void Coding(Programmer Language)
    {
        switch (Language)
        {
            case Programmer.Java:
                Console.ForegroundColor = ConsoleColor.Red;
                Console.WriteLine("Java Coding");
                break;
            case Programmer.CSharp:
                Console.ForegroundColor = ConsoleColor.Blue;
                Console.WriteLine("C# Coding");
                break;
            case Programmer.VBnet:
                Console.ForegroundColor = ConsoleColor.Green;
                Console.WriteLine("VB.NET Coding");
                break;
        }
    }
}

この状態だと、扱う言語が増えた場合は条件分岐を増やさないといけない。
ポリモーフィズムを用いて共通の型を抜き出し、呼び出し元はインターフェースを介して処理を呼び出すようにする。そうすることで実装クラスを直接参照しなくてよくなるので、条件分岐が無くせる。

using System;

class Program
{
    interface IProgrammer
    {
        void Coding();
    }

    class JavaProgrammer : IProgrammer
    {
        void IProgrammer.Coding()
        {
            Console.ForegroundColor = ConsoleColor.Red;
            Console.WriteLine("Java Coding");
        }
    }

    class CSharpProgrammer : IProgrammer
    {
        void IProgrammer.Coding()
        {
            Console.ForegroundColor = ConsoleColor.Blue;
            Console.WriteLine("C# Coding");
        }
    }

    class VBnetProgrammer : IProgrammer
    {
        void IProgrammer.Coding()
        {
            Console.ForegroundColor = ConsoleColor.Green;
            Console.WriteLine("VB.NET Coding");
        }
    }

    static void Main(string[] args)
    {
        IProgrammer[] programmers = 
            { 
                new JavaProgrammer(),
                new CSharpProgrammer(),
                new VBnetProgrammer(),
                new CSharpProgrammer()
            };

        foreach (var p in programmers)
            p.Coding();

        Console.ReadKey();
    }
}

さらに各実装クラスで実行しているメソッドを抽象クラスに抜き出す。
そうすることにより、処理の宣言を一元化し、重複コードを削除する。

using System;

class Program
{
    interface IProgrammer
    {
        void Coding();
    }

    abstract class Programmer : IProgrammer
    {
        protected abstract ConsoleColor GetSkinColor();
        protected abstract string GetContent();

        void IProgrammer.Coding()
        {
            Console.ForegroundColor = GetSkinColor();
            Console.WriteLine(GetContent());
        }
    }

    class JavaProgrammer : Programmer
    {
        protected override ConsoleColor GetSkinColor()
        {
            return ConsoleColor.Red;
        }

        protected override string GetContent()
        {
            return "Java Coding";
        }
    }

    class CSharpProgrammer : Programmer
    {
        protected override ConsoleColor GetSkinColor()
        {
            return ConsoleColor.Blue;
        }

        protected override string GetContent()
        {
            return "C# Coding";
        }
    }

    class VBnetProgrammer : Programmer
    {
        protected override ConsoleColor GetSkinColor()
        {
            return ConsoleColor.Green;
        }

        protected override string GetContent()
        {
            return "VB.NET Coding";
        }
    }

    static void Main(string[] args)
    {
        IProgrammer[] programmers = 
            { 
                new JavaProgrammer(),
                new CSharpProgrammer(),
                new VBnetProgrammer(),
                new CSharpProgrammer()
            };

        foreach (var p in programmers)
            p.Coding();

        Console.ReadKey();
    }
}

サンプルコードの構成はまんま下記ブログを参考にさせていただきましたm(_ _)m
トルネード・プログラミング オブジェクト指向入門 第7回 ポリモーフィズムによる条件分岐の排除

.NETでWMIを用いてOSのバージョン情報、Hotfix、ドライブ情報を取得する

まず、System.Managementの参照を追加してから。

using System;
using System.IO;
using System.Linq;
using System.Management;

class Program
{
    private static string remoteUserName;
    private static string remotePassword;
    private static string remoteServerName;

    static void Main(string[] args)
    {
        try
        {
            remoteUserName = "XXXXX";
            remotePassword = "YYYYY";
            remoteServerName = "ZZZZZ";

            ShowLocalOSVersion();
            ShowRemoteOSVersion();
            ShowWindowsHotFix();
            ShowLocalDiskInfo();
            ShowRemoteDiskInfo();
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
        finally
        {
            Console.ReadLine();
        }
    }

    private static void ShowLocalOSVersion()
    {
        var query = new ObjectQuery("SELECT * FROM Win32_OperatingSystem");
        var searcher = new ManagementObjectSearcher(query);
        ShowOSVersion(searcher);
    }

    private static ManagementScope GetRemoteScope()
    {
        var options = new ConnectionOptions();
        options.Username = string.Join(@"\", remoteServerName, remoteUserName);
        options.Password = remotePassword;

        var scope = new ManagementScope();
        scope.Path.Server = remoteServerName;
        scope.Options = options;
        return scope;
    }

    private static void ShowRemoteOSVersion()
    {
        var query = new ObjectQuery("SELECT * FROM Win32_OperatingSystem");
        var searcher = new ManagementObjectSearcher(GetRemoteScope(), query);
        ShowOSVersion(searcher);
    }

    private static void ShowOSVersion(ManagementObjectSearcher searcher)
    {
        var moc = searcher.Get();

        var spName = "";
        foreach (var mo in moc)
        {
            var osCap = mo["Caption"] == null ? "" : mo["Caption"].ToString();
            var spCap = mo["CSDVersion"] == null ? "" : mo["CSDVersion"].ToString();
            Console.WriteLine("{0} {1}[{2}]", osCap, spName, Environment.OSVersion.Version);
            // => e.g. Microsoft Windows 8 Pro[6.2.9200.0]
        }
    }

    private static void ShowWindowsHotFix()
    {
        var query = new ObjectQuery("SELECT * FROM Win32_QuickFixEngineering");
        var searcher = new ManagementObjectSearcher(query);
        var moc = searcher.Get();

        foreach (var mo in moc)
        {
            var description = mo["Description"] == null ? "" : mo["Description"].ToString();
            var hotFixID = mo["HotFixID"] == null ? "" : mo["HotFixID"].ToString();
            Console.WriteLine("{0}:{1}", description, hotFixID);
            // => e.g. Security Update:KB9999999
        }
    }

    private static void ShowLocalDiskInfo()
    {
        //ローカルは WMI 使わなくても .NET で取れる
        var drives = from drive in DriveInfo.GetDrives()
                     where drive.IsReady && drive.DriveType == DriveType.Fixed
                     select drive;

        foreach (var drive in drives)
        {
            var di = new DriveInfo(drive.Name);
            //サイズは丸めてみる
            var totalSize = DiskSizeRound(di.TotalSize);
            var totalFreeSpace = DiskSizeRound(di.TotalFreeSpace);
            Console.WriteLine("{0} TotalSize:{1} TotalFreeSpace:{2}", drive.Name, totalSize, totalFreeSpace);
        }
    }

    private static void ShowRemoteDiskInfo()
    {
        var query = new ObjectQuery("SELECT * FROM Win32_LogicalDisk Where DriveType=3");
        var searcher = new ManagementObjectSearcher(GetRemoteScope(), query);
        var moc = searcher.Get();

        foreach (var mo in moc)
        {
            var driveName = mo["DeviceID"] == null ? "" : mo["DeviceID"].ToString();
            var totalSize = mo["Size"] == null ? "" : mo["Size"].ToString();
            var totalFreeSpace = mo["FreeSpace"] == null ? "" : mo["FreeSpace"].ToString();
            Console.WriteLine("{0} TotalSize:{1} TotalFreeSpace:{2}", driveName, totalSize, totalFreeSpace);
        }
    }

    private static string DiskSizeRound(long target)
    {
        if (target / 1024 / 1024 > 1000)
            return Math.Round((decimal)(target / 1024 / 1024 / 1024), 1).ToString("#,0.#") + "GB";
        else
            return Math.Round((decimal)(target / 1024 / 1024), 1).ToString("#,0.#") + "MB";
    }
}


WMIって知らなかった。色々取れて面白い。

【.NET】【C#】レジストリからExcelのバージョン取得する

using Microsoft.Win32;
using System;

class Program
{
    static void Main(string[] args)
    {
        try
        {
            var keyData = "";
            using (var regKey = Registry.ClassesRoot.OpenSubKey(@"Excel.Application\CurVer"))
            {
                if (regKey != null)
                    keyData = regKey.GetValue("").ToString();
            }
            Console.WriteLine(GetVersionName(keyData));
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
        finally
        {
            Console.ReadLine();
        }
    }

    private static string GetVersionName(string keyData)
    {
        var name = "Excel ";
        switch (keyData.Substring(keyData.LastIndexOf(".") + 1))
        {
            case "8":
                return name += "97";
            case "9":
                return name += "2000";
            case "10":
                return name += "2002(XP)";
            case "11":
                return name += "2003";
            case "12":
                return name += "2007";
            case "14":
                return name += "2010";
            default:
                return "#N/A";
        }
    }
}


バージョン番号の文字列取得するところがかっこ悪いなー。

WCFで配列の受け渡し(多次元配列とジャグ配列)

WCFで多次元配列の受け渡しをしたくて困ってたら

コレクションのシリアル化
コレクションのシリアル化規則を以下に示します。
・コレクション型は、組み合わせる (コレクションのコレクションを持つ) ことができます。 ジャグ配列は、コレクションのコレクションとして扱われます。 多次元配列はサポートされていません。

データ コントラクトのコレクション型


お、おぅ。

多次元配列はダメだからジャグ配列にしてね、と。
業務コードは多次元配列で色々組んでるから、WCFに飛ばす時にジャグ配列に変換し、返ってきたジャグ配列を多次元配列に戻してあげればいいね。

''' <summary>
''' 二次元配列からジャグ配列に変換します。
''' </summary>
''' <param name="multiArray">二次元配列</param>
''' <returns>ジャグ配列</returns>
''' <remarks></remarks>
Public Function ConvertToJaggedArray(multiArray As String(,)) As String()()
    Dim numOfColumns = multiArray.GetLength(0)
    Dim numOfRows = multiArray.GetLength(1)
    Dim jaggedArray = New String(numOfColumns - 1)() {}

    For c = 0 To numOfColumns - 1
        jaggedArray(c) = New String(numOfRows - 1) {}
        For r = 0 To numOfRows - 1
            jaggedArray(c)(r) = multiArray(c, r)
        Next
    Next
    Return jaggedArray
End Function

''' <summary>
''' ジャグ配列から二次元配列に変換します。
''' </summary>
''' <param name="jaggedArray">ジャグ配列</param>
''' <returns>二次元配列</returns>
''' <remarks></remarks>
Public Function ConvertTo2DArray(jaggedArray As String()()) As String(,)
    Dim numOfColumns = jaggedArray.Length
    Dim numOfRows = jaggedArray(0).Length
    Dim temp2DArray = New String(numOfColumns - 1, numOfRows - 1) {}

    For c = 0 To numOfColumns - 1
        For r = 0 To numOfRows - 1
            temp2DArray(c, r) = jaggedArray(c)(r)
        Next
    Next
    Return temp2DArray
End Function

ここを参考にさせていただきました。
.net - jagged arrays <-> multidimensional arrays conversion in ASP.NET - Stack Overflow

IISのアプリケーション構成をバッチ処理。(appcmd.exe)

毎回作ろうと思うたびに忘れて調べてるのでメモ。

SET APPCMD_PATH=C:\Windows\System32\inetsrv\appcmd.exe
SET SITE_NAME=REPO
SET SITE_PORT=8080
SET SITE_PDIR=C:\REPO
SET APP_NAME=Login
SET APP_PDIR=C:\REPO\Login

::アプリケーションプールの作成
%APPCMD_PATH% add apppool /name:"%SITE_NAME%"

::Webサイトの作成
%APPCMD_PATH% add site /name:"%SITE_NAME%" /bindings:http/*:%SITE_PORT%: /physicalPath:"%SITE_PDIR%"

::アプリケーションの作成
%APPCMD_PATH% add app /site.name:"%SITE_NAME%" /path:/%APP_NAME% /physicalPath:"%APP_PDIR%"

::アプリケーションプールの変更
%APPCMD_PATH% set app /app.name:"%SITE_NAME%/"           /applicationPool:"%SITE_NAME%"
%APPCMD_PATH% set app /app.name:"%SITE_NAME%/%APP_NAME%" /applicationPool:"%SITE_NAME%"

ASP.NET MVC の ORM として PetaPoco を使う(SQL Server CE, Oracle)

ASP.NET MVC で実際を行っている上で、ORM は何がいいのかなーと調べていたら Micro-ORM という言葉をキャッチ。社内でも「Entity Framework はちょっと重厚すぎるし、ある規模を超えると極端に遅いから結局SQLを直で叩くよね」なんて話しを聞いていた。


知ったのは、以下の記事から。
Arroyocode Blog - Micro ORM Data Mapping with PetaPoco and ASP.NET MVC 4

InfoQでも取り上げられている。

PetaPocoは.NETアプリケーションのためのシンO/Rマッパー (ORM) だ。NHibernateやEntity Frameworkといった本格的なORMとは違って、機能の豊富さよりも使いやすさやパフォーマンスに重点が置かれている。PetaPocoはC#ファイル1つでできており、強く型付けされたPOCOで動作し、T4 Templateなどを使ったクラス生成をサポートする。

PetaPocoには次のような非常に興味深い機能がある。

SQL ServerSQL Server CE、MySQLPostgreSQLOracleデータベースで動作する
・Insert/Delete/Update/Save/IsNewのためのヘルパーメソッド
・簡単なトランザクションのサポート
・ページ化されたリクエスト。自動的にレコード全体を分析し、特定のページをフェッチする。
・パラメータ置き換えのサポート。オブジェクトプロパティから名前付きパラメータを取り出せる。
・低摩擦のSQL Builderクラスを含む。
・部分的レコード更新
・データベーススキーマからPOCOクラスを生成するためのT4 Templateを含む。

PetaPoco: .NET向けMicro ORM


日本語だと@neueccさんの記事で触れられている。ここで他の Micro-ORM も知った。パフォーマンステストをされており、参考になった。(本当は鵜呑みにせず自分で測らないとダメなんだけどね。。)
neue cc - C#のMicro-ORM(Dapper, Massive, PetaPoco)について


他のプロダクトも見てみたけど、例えば Dapper はなんとなく記法が好みでなかったりで、PetaPoco を触ってみた。
ASP.NET MVC + PetaPoco のサンプルコードは最初に挙げたリンクの記事についているので、それを参考に。


サクッと試したかったので DB は SQL Server Compact で試した。
チュートリアル: Visual Studio での SQL Server Compact の使用

# 本当は Oracle を使って出来ている既存プロダクトに対して使いたかったんだけど、Oracle を対象に使うのは色々嵌った。Oracle に対して使うにはしっかり事前検証が必要だと思う。これは後述。

web.config に追記して。(Oracle用は無視してください)

  <connectionStrings>
    <add name="OracleConn"
         connectionString="DATA SOURCE=XXX;USER ID=XXX;PASSWORD=XXX"
         providerName="Oracle.DataAccess.Client" />
    <add name="SSCEConn"
         connectionString="Data Source=|DataDirectory|\FlowerShop.sdf"
         providerName="System.Data.SqlServerCe.4.0" />
  </connectionStrings>


基本的には公式ページに書いてある内容を一通り確認。

// 単項目取得
var count = db.ExecuteScalar<int>("SELECT Count(*) FROM Products");
var name = db.ExecuteScalar<string>("SELECT Name FROM Products WHERE ID=@0", 1);

// 1レコード取得(該当データがなかったら null)
var product = db.SingleOrDefault<Products>("SELECT * FROM Products WHERE ID=@0", 1);

// テーブル名、カラム名の省略
var products = db.Fetch<Products>("WHERE UnitPrice = @0", 29.99);

// 名前付きパラメータ
products = db.Fetch<Products>("WHERE UnitPrice = @UnitPrice", new { UnitPrice = 29.99 });

// SQLビルダー
var sql = Sql.Builder
    .Select("*")
    .From("Products")
    .Where("ID < @0", 100)
    .OrderBy("ID DESC");
products = db.Fetch<Products>(sql);

// 新規作成
product = new Products();
product.Name = "新しいお花";
product.UnitPrice = 50;
product.UnitsInStock = 100;
db.Insert(product);

// 更新(レコード全体更新)
product.UnitPrice = 80;
db.Update(product);

// 更新(レコード部分更新)
db.Update<Products>("SET UnitsInStock=@0 WHERE ID=@1", 490, 1);

// 削除1
db.Delete(product);
// 削除2
db.Delete<Products>("WHERE ID=@0", 123);

// ページング
// 5 record/page として、2ページ目に該当する情報を取得
var pageInfo = db.Page<Products>(2, 5, "SELECT * FROM Products ORDER BY Products.ID");
// pageInfo.CurrentPage   : 現在のページ
// pageInfo.ItemsPerPage  : 1ページ当たりのレコード数
// pageInfo.TotalPages    : 全ページ数
// pageInfo.TotalItems    : 全レコード数
// pageInfo.Items         : 該当ページのレコード一覧

// 実行ログ
var sqlLog = db.LastSQL;
var argsLog = db.LastArgs;
var commandLog = db.LastCommand;
// SELECT * FROM Products ORDER BY Products.ID
// OFFSET @0 ROWS FETCH NEXT @1 ROWS ONLY
// 	 -> @0 [Int64] = "5"
// 	 -> @1 [Int64] = "5"

return pageInfo.Items;

うん、いい感じ。


で、上記で触れた Oracle に対して使う場合なんだけど、まずこの PetaPoco ですが、Nugetで

PM> Install-Package PetaPoco

とするとインストールは出来るんだけど、どうやら落ちてくるモジュールが古いみたい。github master branch にコミットされたバグ修正版ではないモジュールが落ちてきます。Nuget で入れたままのものだと Oracle では動かない。

具体的にはこのコミット。
Fix to uppercase escaped table and column names for Oracle (#55) · a645099 · toptensoftware/PetaPoco · GitHub
最新版を Github から手に入れておきましょう。(因みに先に挙げたサンプルコードは最新版使ってた。)

またこのコミットに紐づくコメント(Oracle expects upper-case table / column identifiers when escaped · Issue #55 · toptensoftware/PetaPoco · GitHub)を見ると、 Oracle に対してはあまりテストがされていないみたい。

I've committed a fix to github master branch, but haven't been able to test it as I don't have access to Oracle here right now.

だって。環境ないのかな。これで少し嵌った。


あと他にサンプル通り動かなかったのが2点。
一つは Insert 時の PrimaryKey 自動採番が Oracle では出来ないので、SEQUENCE を作っておく必要がある。PetaPoco - Oracle Support and more... -Topten Software

もう一つは、ページングAPIを叩くときのSQLがサンプル通りだとOracleでは動かない。まぁこれは一回実行するとExceptionのメッセージが出るのですぐにわかる。
PetaPoco.csの以下の部分。

if (_dbType == DBType.Oracle && sqlSelectRemoved.StartsWith("*"))
	throw new Exception("Query must alias '*' when performing a paged query.\neg. select t.* from table t order by t.id");


とこんな感じ。でもやっぱり本体が小さいのはいいね!何かあったとき調査しやすい。
サンプル実装はしばらくこいつをお供にしようと思う。