カテゴリー別アーカイブ: IT

(メモ)BlogEngine.NET 2.5で日本語タイトルからのSlugの生成をマシにする

まだ完璧じゃないけど、メモということで。

admin/Posts/Add_entry.aspx.cs のRaiseCallbackEvent を改造する。

        /// <summary>
        /// Processes a callback event that targets a control.
        /// </summary>
        /// <param name="eventArgument">
        /// A string that represents an event argument to pass to the event handler.
        /// </param>
        public void RaiseCallbackEvent(string eventArgument)
        {
            if (eventArgument.StartsWith("_autosave"))
            {
                var fields = eventArgument.Replace("_autosave", string.Empty).Split(
                    new[] { ";|;" }, StringSplitOptions.None);
                Session["content"] = fields[0];
                Session["title"] = fields[1];
                Session["description"] = fields[2];
                Session["slug"] = fields[3];
                Session["tags"] = fields[4];
            }
            else
            {
                //butaman modify
                System.Text.RegularExpressions.Regex r = new System.Text.RegularExpressions.Regex(@"[^a-zA-Z0-9_\-]");
                System.Text.RegularExpressions.Regex r2 = new System.Text.RegularExpressions.Regex(@"\-{2,}");
                System.Text.RegularExpressions.Regex r3 = new System.Text.RegularExpressions.Regex(@"(^\-|\-$)");
                var result = eventArgument.Trim();
                result = result.Replace(".", "-");
                result = Utils.RemoveIllegalCharacters(result);
                result =  Microsoft.VisualBasic.Strings.StrConv(result,  Microsoft.VisualBasic.VbStrConv.Narrow, 0);
                result = r.Replace(result, "-");
                result = r2.Replace(result, "-");
                result = r3.Replace(result, "");
                callback = result;
            }
        }

我ながら酷いコードだが、こんな感じ。

ただ、Slugを空欄にしてPostすると、従来通りのSlugになってしまう。毎回「Extract from title」リンクボタンを押さないといけないので面倒。根本的には、Utils.RemoveIllegalCharactersを弄る必要があるのだけど、めんどくさいからやらない。

BlogEngine.NETをリバースプロキシ環境で使う(Global.asaxを改造)

このBlogはリバースプロキシ環境下で動作している。フロントエンドはApache(blog.ebuta.net:80)で、バックエンドがIIS(blog.ebuta.net:8081)である。

さて、バックエンドのIISでBlogEngine.NET 2.5を動作させると、非常に困ったことが発生する。一部のメニューのリンク等がバックエンドのURLを指してしまうのだ。例えば、ホームへのリンク。こちらとしては「http://blog.ebuta.net/」を指して欲しいのに、実際に表示されるリンクは「http://blog.ebuta.net:8081/」を指してしまう。この問題点は リバースプロキシの確認 – kiyoeri BlogEngine でも指摘されている。この問題の原因は、BlogEngine.NETがリクエストのURLをもとに絶対URLを取得していることである。これをできるだけシンプルな方法で解決したい。

ソースを調べてみると、各テーマにおいて、絶対URLの取得に「Utils.AbsoluteWebRoot」を参照していることが分かる。ここで、修正方法は2つ考えられる。

  1. テーマを修正する
  2. Utils.AbsoluteWebRootを修正する

1はシンプルだが、根本的なところが治っていないので、他のところでボロが出そうである。となると、2のUtils.AbsoluteWebRootの修正であるが、実はUtilsクラスはコンパイル済のアセンブリ(DLL)の中にあり、修正にはVisualStudioを使ってソースからコンパイルし直す必要がある。別にコンパイルするのは構わないのだが、バージョンが上がるたびにコンパイルをしなおすのはいかにも面倒だ。他の方法がないか手がかりを探すため、まず、Utils.AbsoluteWebRootのソースを覗いてみる。

        /// <summary>
        ///     Gets the absolute root of the website.
        /// </summary>
        /// <value>A string that ends with a '/'.</value>
        public static Uri AbsoluteWebRoot
        {
            get
            {
                var context = HttpContext.Current;
                if (context == null)
                {
                    throw new WebException("The current HttpContext is null");
                }

                var absoluteurl = context.Items["absoluteurl"];
                if (absoluteurl == null)
                {
                    absoluteurl = new Uri(context.Request.Url.GetLeftPart(UriPartial.Authority) + RelativeWebRoot);
                    context.Items["absoluteurl"] = absoluteurl;
                }

                return absoluteurl as Uri;

            }
        }

 このソースを読む限り、リクエストごとに、最初はURLを元に絶対URLを作成し、以降は context.Items[“absoluteurl”]にキャッシュされるようになっている。つまり、AbsoluteWebRootが呼ばれる前(あるいは、テーマがレンダリングされる前)に、context.Items[“absoluteurl”]を設定してしまえばいいのである。そこで、Global.asaxを改変して、この処理を追加することにする。

Global.asaxのApplication_BeginRequestを見てみる。

    void Application_BeginRequest(object source, EventArgs e)
    {
        HttpApplication app = (HttpApplication)source;
        HttpContext context = app.Context;
        
        // Attempt to perform first request initialization
        FirstRequestInitialization.Initialize(context);
    }

これを、以下のように改変する。

    void Application_BeginRequest(object source, EventArgs e)
    {
        HttpApplication app = (HttpApplication)source;
        HttpContext context = app.Context;
        //追加: リバースプロキシを使ってるので、絶対パスを手で指定しないといけない
        context.Items["absoluteurl"] = new Uri("http://blog.ebuta.net/");
        // Attempt to perform first request initialization
        FirstRequestInitialization.Initialize(context);
    }

なお、URLは環境に合わせて書き換えること。

こうすることで、リンクに正しいURLが出力されるようになった。

実際には、Extentionとして実装するのがスマートなのかも知れないが、いまのところまだ仕様がよく分かっていないので別の機会に試して見ようと思う。

 

<追記>

AbsoluteWebRootを使用していない箇所があるようだ。

post.aspx.csの以下の部分。

                    if (BlogSettings.Instance.EnablePingBackReceive)
                    {
                        Response.AppendHeader("x-pingback", "http://" + Request.Url.Authority + Utils.RelativeWebRoot + "pingback.axd");
                    }

これを編集して以下のように改変する。

                    if (BlogSettings.Instance.EnablePingBackReceive)
                    {
                        Response.AppendHeader("x-pingback", Utils.AbsoluteWebRoot + "pingback.axd");
                    }

 

SourceMonitor便利そう

会社ではだいたい毎日コーディングをやっているが、自分のコードの品質が果たして良いのか悪いのか分からない感じになっている。そんなに酷いコードを書かないようにしているけれど、うちの部署にはコードレビューという手続きが確立されていないので、客観的に見て良いコードか悪いコードかわかりにくい。

一般的にメンテナンスのしやすいコード、テストのしやすいコードが良いコードといわれている。シンプルで分かりやすいコード、条件分岐が多すぎないコードが良いとされ、複雑なコードは悪いとされる。この良し悪しを計測し数値化する「メトリクス計測」という手法がある。

「メトリクス計測」のソフトの中でも、SourceMonitorというフリーソフトをお勧めしたい。このソフトは行数やコメントの割合、複雑度(McCabeのサイクロマティック数)などを計測するツールだ。この中でも複雑度として計測される値は、C1分岐網羅に必要なケース数に近く、これが大きすぎると実質的に単体テストの実施が不可能になると考えられる。

10 以下であればよい構造

30 を越える場合,構造に疑問

50 を越える場合,テストが不可能

75 を越える場合,いかなる変更も誤修正を生む原因を作る

(http://forza.cocolog-nifty.com/blog/2009/01/post-ca39.html より引用)

これは判断基準としては分かりやすい。

実際に、卒業研究で作成したプログラムを計測してみると、複雑度(Complexity)が最も大きかったのが、ファイルIOを担当するクラスの、チェックを行う関数だった。

卒論のプログラムをSourceMonitorで分析した結果

ソースを抜粋してみると確かにこれは酷い。もう自分でも読めない感じ。でも数式を素直にプログラムしただけなので、やむを得ない部分もあるかもしれない。

            bool changed;
            do
            {
                changed = false;
                for (int i = 0; i < length; i++)
                {
                    for (int j = 0; j < length; j++)
                    {
                        if (table[i, j])
                        {
                            for (int k = 0; k < length; k++)
                            {
                                changed |= !table[i, k] && table[j, k];
                                table[i, k] |= table[j, k];
                            }
                        }
                    }
                }

            } while (changed);

 

ちなみに、このソフトは対応している言語も、C++、 C、 C#、 VB.NET、 Java、 Delphiと幅広く、関数レベルでの分析は出来ないがVB6、HTMLにも対応している。フリーソフトとしては十分すぎる機能だろう。

ダウンロードは以下から。

http://www.campwoodsw.com/sourcemonitor.html

Vista用の既定MIDIデバイスを選択する MidiSelector公開

ブタマン君プロジェクトは、既定のMIDIデバイスを選択するGUIツールを公開します。
画像

Windows VistaのコントロールパネルにはMIDIマッパーの設定項目が存在しておらず、設定の変更にはレジストリを直接触る必要がありました。

MidiSelectorはこれをグラフィカルに設定できるようにしたものです。画面は従来のWindowsと似たユーザーインタフェースになっているため、操作は容易です。

また、インストールを必要としません。

対応OSはWindows Vista各エディション。動作には.Net Framework 2.0が必要です。その他のOSではご使用なさいませんようお願いします。

ダウンロードは下のリンクか、またはソフトウェアのダウンロードコーナーから行えます。
download MidiSelector.zip

プログラミングメモ:C#でノートPCの液晶の輝度を制御する(Vista限定)

VistaではWMIを通してコントロール(管理)できるものが増えました。液晶の輝度調整もその一つです。

今回は、WmiMonitorBrightnessを使用した液晶の輝度(明るさ)を制御する方法をご紹介します。

*****************************************
//using System.Management;
//とすること。

//初期化//

//ManagementClassを取得し、操作することになる
ManagementClass WmiMonitorBrightness, WmiMonitorBrightnessMethods;

//主に現在の輝度を取得するためのオブジェクト
WmiMonitorBrightness = new ManagementClass(“root\wmi”, @”WmiMonitorBrightness”, null);

//主に輝度を設定するためのオブジェクト
WmiMonitorBrightnessMethods = new ManagementClass(“root\wmi”, @”WmiMonitorBrightnessMethods”, null);

*****************************************

ここまでは初期化のコードを書きました。
では、手始めに輝度を取得してみましょう。

*****************************************

//WmiMonitorBrightness.GetInstances()
//はコントロールできるもの(モニタ?)の数だけ
//ManagementObjectインスタンスを返す。

foreach (ManagementObject mo in WmiMonitorBrightness.GetInstances())
{
_currentBrightness =(int)mo[“CurrentBrightness”];
//WMIオブジェクト?のプロパティを参照するときは、上記のようにする
//CurrentBrightnessはパーセント単位で現在の輝度を返す

break; //この例では一番最初のものの結果だけを取得

}
*****************************************

輝度を設定するには次のように書きます。

*****************************************
foreach (ManagementObject mo in WmiMonitorBrightnessMethods.GetInstances())
{
ManagementBaseObject inParams = mo.GetMethodParameters(“WmiSetBrightness”);
//WmiSetBrightnessというWMIのメソッドを呼び出すために
//必要なパラメータのリスト?を取得する

inParams[“Brightness”] = 50; //輝度を50%に
inParams[“Timeout”] = 5; //操作のタイムアウトを5秒にセット

//WmiSetBrightnessを呼び出す
//InovokeMethodの引数にはinParamsを指定する。

ManagementBaseObject outParams = mo.InvokeMethod(“WmiSetBrightness”, inParams, null);

//outParamsには結果が返ってくるが、この例では使わない
}

これをクラスにしてみました。
表示義務を除いた修正BSD Licenseとしますので、著作権は保持しますが適当に使っていただいて結構です。

ソースファイルをダウンロード

プログラミングメモ: .net CF でDelegate.DynamicInvokeもどき

DynamicInvokeを使うと以下のような呼び出し方(遅延バインディング)ができるので使い方によっては便利です。(以下の例はC#)

Delegate method = new FooDelegate(Bar); //Foo、Barは例
method.DynamicInvoke(a, b, c); //a、b、cは例

これを使うとイベントの発生を集中管理するためのプロキシ的な関数を作ることもできます。

しかしながら、Compact Frameworkでは、Type.InvokeMethod()やControl.Invoke()はサポートされている割に、意外にもDelegate.DynamicInvoke()はサポートされていません。

少なくともW-ZERO3の環境(WM5, CF2.0)ではControl.Invoke()で代用しても例外は発生しないのですが、Windows XPなどPCではコントロールと別スレッドから呼び出ししなければ例外が発生してしまうようです。(もともと用途が違いますから)

単純にPC版.net Frameworkとソースコードを共通化したい場合には、プリプロセッサを使用できますが、一方でCompact Frameworkの実行ファイルをPC上で実行したい場合などは次のようにすることもできます。

Delegate method = new FooDelegate(Bar); //Foo、Barは例
method.GetType().GetMethod(“Invoke”).Invoke(method, a, b, c);

ただし、実際には環境依存な部分もあるのでtry-catchステートメントで想定外の例外を拾ってやる必要があるかもしれません。

ちなみに、Invoke系メソッドの対象のデリゲートがイベントである場合は、中身を調べたりしなくても登録されているイベントハンドラすべてが、ちゃんと呼び出されます。

.Net Compact Framework 2.0 + W-ZERO3[es]で動作を確認しています。