SHOJI's Code
 仕事や趣味で書いた各種言語のプログラミングコード(エクセルVBA,PHP,C/C++/C#,JavaScript等)、その他雑記。
2018.04<<12345678910111213141516171819202122232425262728293031>>2018.06
上記の広告は1ヶ月以上更新のないブログに表示されています。
新しい記事を書く事で広告が消せます。

ASP.NETプログラミングしているときに、クライアントコールバックなるものを知った。
ポストバックと違いクライアント側が再読み込みなどされないから、使えるな、と思ったのだが、それを呼び出すためのクライアントスクリプトを、ちまちまとコールバックの種類の数だけ GetCallbackEventReferenceメソッドを使って作成しなければならない。

・・・これは面倒くさい

ってことで、ヘルパークラスを作った。使い方はこんな感じ。

まず、コールバック用の関数にそれを示す属性をつける。
[CallbackEventHandlerMethod]
public void SelectRow(int row) {
...
}

[CallbackEventHanderMethod]
public int GetIndex() {
...
return index;
}


ページのロード時、クライアントスクリプトを作成する
protected void Page_Load(object sender, EventArgs e) {
...
CallbackEventHandlerHelper.RegisterClientScript(this, true);
...
}


ICallbackEventHandlerインターフェイスで実装するRaiseCallbackEventメソッドとGetCallbackResultメソッドはこんな感じに
public string GetCallbackResult()
{
return CallbackEventHandlerHelper.ResultString;
}
public void RaiseCallbackEvent(string eventArgument)
{
CallbackEventHandlerHelper.Invoke(this, eventArgument);
}


クライアント側のJavaScriptでは、先のCallbackEventHandlerMethod属性のついたメソッドと同名の関数を呼び出す
<button onclick='SelectRow(1);'>選択</button>

戻り値のあるものは戻り値がコールバックされる
GetIndex(function(result){
selectedRow = result;
});



かなりすっきりした。
/// <summary>
/// クライアントコールバックイベント処理ヘルパー
/// </summary>
[Serializable]
public class CallbackEventHandlerHelper
{
/// <summary>
/// 戻り値
/// </summary>
public static object Result { get; private set; }

/// <summary>
/// 戻り値の文字列
/// </summary>
public static string ResultString
{
get
{
return Result != null ? Result.ToString() : string.Empty;
}
}

/// <summary>
/// クライアントスクリプトを登録します
/// </summary>
/// <param name="page">ページ</param>
/// <param name="addScriptTag">SCRIPTタグを付加する場合true</param>
public static void RegisterClientScript(Page page, bool addScriptTag)
{
page.ClientScript.RegisterClientScriptBlock(page.GetType(), page.GetType().Name, GetClientScript(page), addScriptTag);
}

/// <summary>
/// クライアントスクリプトを取得します
/// </summary>
/// <param name="page">ページ</param>
/// <returns>スクリプト</returns>
public static string GetClientScript(Page page)
{
string script = string.Empty;

foreach (MethodInfo mi in GetMethods(page))
{
ParameterInfo[] pis = mi.GetParameters();
string[] pn = (from pi in pis select pi.Name).ToArray();
string[] px = (from pi in pis select pi.ParameterType == typeof(string) ? "escape(" + pi.Name + ")" : pi.Name).ToArray();

string pd = string.Join(",", pn);
string ea = "'" + mi.Name + (pn.Length > 0 ? "$'+" + string.Join("+'$'+", px) : "'");

string cb = "null";

if (isFunction(mi))
{
string cp = "__cb";
pd += (pd != string.Empty ? "," : "") + cp;
cb = "function(o){if(" + cp + ") " + cp + "(o);}";
}

script += "function " + mi.Name + "(" + pd + "){" + page.ClientScript.GetCallbackEventReference(page, ea, cb, "", isAsync(mi))+";}\r\n";
}

return script;
}

/// <summary>
/// イベント処理を実行します
/// </summary>
/// <param name="eventArgument">イベントパラメータ文字列</param>
/// <returns>戻り値</returns>
public static object Invoke(Page page, string eventArgument)
{
MethodInfo mi;
object[] args;
GetMethodAndArguments(page, eventArgument, out mi, out args);

Result = null;
object ret = mi.Invoke(page, args);

if (isFunction(mi)) Result = ret;

return ret;
}

/// <summary>
/// イベントパラメータ文字列からイベント処理メソッド名を取得します
/// </summary>
/// <param name="eventArgument">イベントパラメータ</param>
/// <returns>メソッド名</returns>
public static string GetMethodName(string eventArgument)
{
return getMethodAndArguments(eventArgument)[0];
}

/// <summary>
/// イベントパラメータ文字列からイベント処理メソッドを取得します
/// </summary>
/// <param name="page">ページ</param>
/// <param name="eventArgument">イベントパラメータ</param>
/// <returns>メソッド</returns>
public static MethodInfo GetMethod(Page page, string eventArgument)
{
string name = GetMethodName(eventArgument);
return findMethod(GetMethods(page), name);
}

/// <summary>
/// クライアントコールバックイベント処理メソッドの一覧を取得します
/// </summary>
/// <param name="page">ページ</param>
/// <returns>メソッドの一覧</returns>
public static MethodInfo[] GetMethods(Page page)
{
return page.GetType().GetMethods().Where(mi => hasAttribute(mi)).ToArray();
}

/// <summary>
/// 引数の取得
/// </summary>
/// <param name="page">ページ</param>
/// <param name="eventArgument">イベントパラメータ文字列</param>
/// <returns>引数の配列</returns>
public static object[] GetArguments(Page page, string eventArgument)
{
MethodInfo mi;
object[] args;
GetMethodAndArguments(page, eventArgument, out mi, out args);
return args;
}

/// <summary>
/// イベントパラメータ文字列からイベント処理メソッドと引数を取得します
/// </summary>
/// <param name="page">ページ</param>
/// <param name="eventArgument">イベントパラメータ文字列</param>
/// <param name="method">イベント処理メソッド</param>
/// <param name="args">引数</param>
public static void GetMethodAndArguments(Page page, string eventArgument, out MethodInfo method, out object[] args)
{
string[] ma = getMethodAndArguments(eventArgument);
method = findMethod(GetMethods(page), ma[0]);
args = arguments(method, ma.Skip(1).ToArray()).ToArray();
}


// 非同期か?
private static bool isAsync(MethodInfo method)
{
CallbackEventHandlerMethodAttribute a = getAttribute(method);
return a.Async;
}

// 関数か?
private static bool isFunction(MethodInfo method)
{
return (method != null && method.ReturnType != typeof(void));
}

// 引数の列挙
private static IEnumerable<object> arguments(MethodInfo method, string[] args)
{
int a = 0;
foreach (ParameterInfo pi in method.GetParameters())
{
yield return a<args.Length ? convertArgument(pi.ParameterType, args[a++]) : null;
}
}

// 文字列で指定されたパラメータを目的の型に変換する
private static object convertArgument(Type type, string value)
{
return type == typeof(string) ? HttpUtility.UrlDecode(value) : convert(type, value);
}

// 文字列から指定した型を変換する
private static object convert(Type type, string value)
{
MethodInfo parser = type.GetMethod("Parse", new Type[] { typeof(string) });
return parser != null ? parser.Invoke(null, new object[] { value }) : Convert.ChangeType(value, type);
}

// メソッド名と引数の配列を返す
private static string[] getMethodAndArguments(string eventArgument)
{
return eventArgument.Split('$');
}

// メソッドの配列から指定した名前のメソッドを検索する
private static MethodInfo findMethod(MethodInfo[] methods, string name)
{
return methods.FirstOrDefault(mi => mi.Name == name);
}

// CakkbackEventHandlerMethod属性を持つかどうかを返す
private static bool hasAttribute(MethodInfo method)
{
return getAttribute(method) != null;
}

// CallbackEventHandlerMethod属性を取得する
private static CallbackEventHandlerMethodAttribute getAttribute(MethodInfo method)
{
object[] os = method.GetCustomAttributes(typeof(CallbackEventHandlerMethodAttribute), false);
return os.Length > 0 ? os[0] as CallbackEventHandlerMethodAttribute : null;
}
}

/// <summary>
/// クライアントコールバックイベント処理メソッドとして使用することを示します
/// </summary>
[AttributeUsage(AttributeTargets.Method, AllowMultiple=true, Inherited=false)]
public class CallbackEventHandlerMethodAttribute : Attribute
{
/// <summary>
/// クライアントコールバックイベント処理メソッドとして使用することを示します
/// </summary>
public CallbackEventHandlerMethodAttribute()
{
Async = true;
}

/// <summary>
/// 非同期モード
/// </summary>
public bool Async { get; set; }
}


テーマ:プログラミング - ジャンル:コンピュータ
コメント
この記事へのコメント
コメントを投稿する

管理者にだけ表示を許可する
トラックバック
この記事のトラックバックURL
この記事へのトラックバック
copyright © 2004-2006 SHOJI, Powered By FC2ブログ all rights reserved.
上記広告は1ヶ月以上更新のないブログに表示されています。新しい記事を書くことで広告を消せます。